Skip to content

Commit 80f2436

Browse files
authored
Merge 01a8a1d into 64ba963
2 parents 64ba963 + 01a8a1d commit 80f2436

File tree

4 files changed

+103
-75
lines changed

4 files changed

+103
-75
lines changed

docs/docs/dotnet-api-docs.md

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -154,45 +154,18 @@ To disable the default filtering rules, set the `disableDefaultFilter` property
154154

155155
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.
156156

157-
There are two ways of customizing the API filters:
157+
### The `<exclude />` documentation comment
158158

159-
### Custom with Code
159+
The `<exclude />` documentation comment excludes the type or member on a per API basis using C# documentation comment:
160160

161-
To use a custom filtering with code:
162-
163-
1. Use docfx .NET API generation as a NuGet library:
164-
165-
```xml
166-
<PackageReference Include="Docfx.Dotnet" Version="2.62.0" />
167-
```
168-
169-
2. Configure the filter options:
170-
171-
```cs
172-
var options = new DotnetApiOptions
173-
{
174-
// Filter based on types
175-
IncludeApi = symbol => ...
176-
177-
// Filter based on attributes
178-
IncludeAttribute = symbol => ...
179-
}
180-
181-
await DotnetApiCatalog.GenerateManagedReferenceYamlFiles("docfx.json", options);
161+
```csharp
162+
/// <exclude />
163+
public class Foo { }
182164
```
183165

184-
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.
185-
186-
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.
187-
188-
Hiding the parent symbol also hides all of its child symbols, e.g.:
189-
- If a namespace is hidden, all child namespaces and types underneath it are hidden.
190-
- If a class is hidden, all nested types underneath it are hidden.
191-
- If an interface is hidden, explicit implementations of that interface are also hidden.
192-
193-
### Custom with Filter Rules
166+
### Custom filter rules
194167

195-
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:
168+
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:
196169

197170
```json
198171
{
@@ -265,3 +238,38 @@ apiRules:
265238
```
266239

267240
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.
241+
242+
243+
### Custom code filter
244+
245+
To use a custom filtering with code:
246+
247+
1. Use docfx .NET API generation as a NuGet library:
248+
249+
```xml
250+
<PackageReference Include="Docfx.Dotnet" Version="2.62.0" />
251+
```
252+
253+
2. Configure the filter options:
254+
255+
```cs
256+
var options = new DotnetApiOptions
257+
{
258+
// Filter based on types
259+
IncludeApi = symbol => ...
260+
261+
// Filter based on attributes
262+
IncludeAttribute = symbol => ...
263+
}
264+
265+
await DotnetApiCatalog.GenerateManagedReferenceYamlFiles("docfx.json", options);
266+
```
267+
268+
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.
269+
270+
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.
271+
272+
Hiding the parent symbol also hides all of its child symbols, e.g.:
273+
- If a namespace is hidden, all child namespaces and types underneath it are hidden.
274+
- If a class is hidden, all nested types underneath it are hidden.
275+
- If an interface is hidden, explicit implementations of that interface are also hidden.

src/Docfx.Dotnet/SymbolFilter.cs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,25 @@ public SymbolFilter(ExtractMetadataConfig config, DotnetApiOptions options)
2626

2727
public bool IncludeApi(ISymbol symbol)
2828
{
29-
return !IsCompilerGeneratedDisplayClass(symbol) && IsSymbolAccessible(symbol) && IncludeApiCore(symbol);
30-
31-
bool IncludeApiCore(ISymbol symbol)
29+
return _cache.GetOrAdd(symbol, _ =>
3230
{
33-
return _cache.GetOrAdd(symbol, _ => _options.IncludeApi?.Invoke(_) switch
34-
{
35-
SymbolIncludeState.Include => true,
36-
SymbolIncludeState.Exclude => false,
37-
_ => IncludeApiDefault(symbol),
38-
});
39-
}
31+
return !IsCompilerGeneratedDisplayClass(symbol) &&
32+
IsSymbolAccessible(symbol) &&
33+
!HasExcludeDocumentComment(symbol) &&
34+
_options.IncludeApi?.Invoke(_) switch
35+
{
36+
SymbolIncludeState.Include => true,
37+
SymbolIncludeState.Exclude => false,
38+
_ => IncludeApiDefault(symbol),
39+
};
40+
});
4041

4142
bool IncludeApiDefault(ISymbol symbol)
4243
{
4344
if (_filterRule is not null && !_filterRule.CanVisitApi(RoslynFilterData.GetSymbolFilterData(symbol)))
4445
return false;
4546

46-
return symbol.ContainingSymbol is null || IncludeApiCore(symbol.ContainingSymbol);
47+
return symbol.ContainingSymbol is null || IncludeApi(symbol.ContainingSymbol);
4748
}
4849

4950
static bool IsCompilerGeneratedDisplayClass(ISymbol symbol)
@@ -54,24 +55,22 @@ static bool IsCompilerGeneratedDisplayClass(ISymbol symbol)
5455

5556
public bool IncludeAttribute(ISymbol symbol)
5657
{
57-
return IsSymbolAccessible(symbol) && IncludeAttributeCore(symbol);
58-
59-
bool IncludeAttributeCore(ISymbol symbol)
58+
return _attributeCache.GetOrAdd(symbol, _ =>
6059
{
61-
return _attributeCache.GetOrAdd(symbol, _ => _options.IncludeAttribute?.Invoke(_) switch
60+
return IsSymbolAccessible(symbol) && !HasExcludeDocumentComment(symbol) && _options.IncludeAttribute?.Invoke(_) switch
6261
{
6362
SymbolIncludeState.Include => true,
6463
SymbolIncludeState.Exclude => false,
6564
_ => IncludeAttributeDefault(symbol),
66-
});
67-
}
65+
};
66+
});
6867

6968
bool IncludeAttributeDefault(ISymbol symbol)
7069
{
7170
if (_filterRule is not null && !_filterRule.CanVisitAttribute(RoslynFilterData.GetSymbolFilterData(symbol)))
7271
return false;
7372

74-
return symbol.ContainingSymbol is null || IncludeAttributeCore(symbol.ContainingSymbol);
73+
return symbol.ContainingSymbol is null || IncludeAttribute(symbol.ContainingSymbol);
7574
}
7675
}
7776

@@ -127,4 +126,12 @@ bool IsEiiAndIncludesContainingSymbols(IEnumerable<ISymbol> symbols)
127126
return symbols.Any() && symbols.All(s => IncludeApi(s.ContainingSymbol));
128127
}
129128
}
129+
130+
private static bool HasExcludeDocumentComment(ISymbol symbol)
131+
{
132+
return symbol.GetDocumentationCommentXml() is { } xml && (
133+
xml.Contains("<exclude/>") ||
134+
xml.Contains("<exclude>") ||
135+
xml.Contains("<exclude "));
136+
}
130137
}

test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3709,4 +3709,25 @@ public interface IFoo { void Bar(); }
37093709
Assert.Equal("public class Foo : IFoo", foo.Syntax.Content[SyntaxLanguage.CSharp]);
37103710
Assert.Equal("void IFoo.Bar()", foo.Items[0].Syntax.Content[SyntaxLanguage.CSharp]);
37113711
}
3712+
3713+
[Fact]
3714+
public void TestExcludeDocumentationComment()
3715+
{
3716+
var code =
3717+
"""
3718+
namespace Test
3719+
{
3720+
public class Foo
3721+
{
3722+
/// <exclude />
3723+
public void F1() {}
3724+
}
3725+
}
3726+
""";
3727+
3728+
var output = Verify(code);
3729+
var foo = output.Items[0].Items[0];
3730+
Assert.Equal("public class Foo", foo.Syntax.Content[SyntaxLanguage.CSharp]);
3731+
Assert.Empty(foo.Items);
3732+
}
37123733
}

test/docfx.Snapshot.Tests/SamplesTest.Seed.Windows/pdf/toc.pdf.verified.json

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -712,60 +712,52 @@
712712
"PageNumber": 16,
713713
"Coordinates": {
714714
"Left": 28,
715-
"Top": 211.24994
715+
"Top": 552.50006
716716
}
717717
}
718718
}
719719
]
720720
},
721721
{
722722
"Number": 15,
723-
"NumberOfImages": 1,
724-
"Text": "15ImageMermaid DiagramsFlowchartCode SnippetThe example highlights lines 2, line 5 to 7 and lines 9 to the end of the file.MY TODOThis is a TODO.TextOneTwoHardRoundDecisionResult 1Result 2",
725-
"Links": [
726-
{
727-
"Uri": "https://learn.microsoft.com/en-us/media/learn/not-found/learn-not-found-light-mode.png?branch=main"
728-
},
729-
{
730-
"Uri": "https://learn.microsoft.com/en-us/media/learn/not-found/learn-not-found-light-mode.png?branch=main"
731-
}
732-
]
733-
},
734-
{
735-
"Number": 16,
736-
"Text": "16Math ExpressionsThis sentence uses $ delimiters to show math inline: The Cauchy-Schwarz InequalityThis expression uses \\$ to display a dollar sign: To split $100 in half, we calculate using System;using Azure;using Azure.Storage;using Azure.Storage.Blobs;class Program{ static void Main(string[] args) { // Define the connection string for the storage account string connectionString = \"DefaultEndpointsProtocol=https;AccountName=<your-account-name>;AccountKey=<your-account-key>;EndpointSuffix=core.windows.net\"; // Create a new BlobServiceClient using the connection string var blobServiceClient = new BlobServiceClient(connectionString); // Create a new container var container = blobServiceClient.CreateBlobContainer(\"mycontainer\"); // Upload a file to the container using (var fileStream = File.OpenRead(\"path/to/file.txt\")) { container.UploadBlob(\"file.txt\", fileStream); } // Download the file from the container var downloadedBlob = container.GetBlobClient(\"file.txt\").Download(); using (var fileStream = File.OpenWrite(\"path/to/downloaded-file.txt\")) { downloadedBlob.Value.Content.CopyTo(fileStream); } }}",
723+
"Text": "15ImageMermaid DiagramsFlowchartCode SnippetThe example highlights lines 2, line 5 to 7 and lines 9 to the end of the file.MY TODOThis is a TODO.TextOneTwoHardRoundDecisionResult 1Result 2using System;using Azure;using Azure.Storage;using Azure.Storage.Blobs;class Program{ static void Main(string[] args) { // Define the connection string for the storage account string connectionString = \"DefaultEndpointsProtocol=https;AccountName=<your-account-name>;AccountKey=<your-account-key>;EndpointSuffix=core.windows.net\"; // Create a new BlobServiceClient using the connection string var blobServiceClient = new BlobServiceClient(connectionString); // Create a new container var container = blobServiceClient.CreateBlobContainer(\"mycontainer\"); // Upload a file to the container using (var fileStream = File.OpenRead(\"path/to/file.txt\"))",
737724
"Links": []
738725
},
739726
{
740-
"Number": 17,
741-
"Text": "17Custom Syntax HighlightingTabsLinuxWindowsThe above tab group was created with the following syntax:Tabs are indicated by using a specific link syntax within a Markdown header. The syntax can be describedas follows:A tab starts with a Markdown header, #, and is followed by a Markdown link [](). The text of the link willbecome the text of the tab header, displayed to the customer. In order for the header to be recognizedas a tab, the link itself must start with #tab/ and be followed by an ID representing the content of thetab. The ID is used to sync all same-ID tabs across the page. Using the above example, when a userselects a tab with the link #tab/windows, all tabs with the link #tab/windows on the page will be selected.Dependent tabsIt's possible to make the selection in one set of tabs dependent on the selection in another set of tabs.Here's an example of that in action:resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { name: 'hello' // (...)}Content for Linux...# [Linux](#tab/linux)Content for Linux...# [Windows](#tab/windows)Content for Windows...---# [Tab Display Name](#tab/tab-id)",
727+
"Number": 16,
728+
"Text": "16Math ExpressionsThis sentence uses $ delimiters to show math inline: The Cauchy-Schwarz InequalityThis expression uses \\$ to display a dollar sign: To split $100 in half, we calculate Custom Syntax HighlightingTabsLinuxWindowsThe above tab group was created with the following syntax: { container.UploadBlob(\"file.txt\", fileStream); } // Download the file from the container var downloadedBlob = container.GetBlobClient(\"file.txt\").Download(); using (var fileStream = File.OpenWrite(\"path/to/downloaded-file.txt\")) { downloadedBlob.Value.Content.CopyTo(fileStream); } }}resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = { name: 'hello' // (...)}Content for Linux...# [Linux](#tab/linux)",
742729
"Links": [
743730
{
744731
"Goto": {
745-
"PageNumber": 17,
732+
"PageNumber": 16,
746733
"Coordinates": {
747734
"Left": 28,
748-
"Top": 574.99994
735+
"Top": 181.24994
749736
}
750737
}
751738
}
752739
]
753740
},
754741
{
755-
"Number": 18,
756-
"Text": "18.NETTypeScriptREST APINotice how changing the Linux/Windows selection above changes the content in the .NET andTypeScript tabs. This is because the tab group defines two versions for each .NET and TypeScript, wherethe Windows/Linux selection above determines which version is shown for .NET/TypeScript. Here's themarkup that shows how this is done:.NET content for Linux...# [.NET](#tab/dotnet/linux).NET content for Linux...# [.NET](#tab/dotnet/windows).NET content for Windows...# [TypeScript](#tab/typescript/linux)TypeScript content for Linux...# [TypeScript](#tab/typescript/windows)TypeScript content for Windows...# [REST API](#tab/rest)REST API content, independent of platform...---",
742+
"Number": 17,
743+
"Text": "17Tabs are indicated by using a specific link syntax within a Markdown header. The syntax can be describedas follows:A tab starts with a Markdown header, #, and is followed by a Markdown link [](). The text of the link willbecome the text of the tab header, displayed to the customer. In order for the header to be recognizedas a tab, the link itself must start with #tab/ and be followed by an ID representing the content of thetab. The ID is used to sync all same-ID tabs across the page. Using the above example, when a userselects a tab with the link #tab/windows, all tabs with the link #tab/windows on the page will be selected.Dependent tabsIt's possible to make the selection in one set of tabs dependent on the selection in another set of tabs.Here's an example of that in action:.NETTypeScriptREST APINotice how changing the Linux/Windows selection above changes the content in the .NET andTypeScript tabs. This is because the tab group defines two versions for each .NET and TypeScript, wherethe Windows/Linux selection above determines which version is shown for .NET/TypeScript. Here's themarkup that shows how this is done:Content for Linux...# [Windows](#tab/windows)Content for Windows...---# [Tab Display Name](#tab/tab-id).NET content for Linux...# [.NET](#tab/dotnet/linux).NET content for Linux...# [.NET](#tab/dotnet/windows).NET content for Windows...",
757744
"Links": [
758745
{
759746
"Goto": {
760-
"PageNumber": 18,
747+
"PageNumber": 17,
761748
"Coordinates": {
762749
"Left": 28,
763-
"Top": 732.49994
750+
"Top": 324.49994
764751
}
765752
}
766753
}
767754
]
768755
},
756+
{
757+
"Number": 18,
758+
"Text": "18# [TypeScript](#tab/typescript/linux)TypeScript content for Linux...# [TypeScript](#tab/typescript/windows)TypeScript content for Windows...# [REST API](#tab/rest)REST API content, independent of platform...---",
759+
"Links": []
760+
},
769761
{
770762
"Number": 19,
771763
"Text": "19ClassesClass1This is a test class.StructsIssue5432Namespace BuildFromAssembly",

0 commit comments

Comments
 (0)