Skip to content

Commit 6d9fd19

Browse files
authored
Merge 554c3db into 64ba963
2 parents 64ba963 + 554c3db commit 6d9fd19

File tree

3 files changed

+92
-52
lines changed

3 files changed

+92
-52
lines changed

docs/docs/dotnet-api-docs.md

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ To disable markdown parsing while processing XML tags, set `shouldSkipMarkup` to
146146
}
147147
```
148148

149+
In addition, docfx supports these C# documentation comments:
150+
151+
### `<exclude>`
152+
149153
## Filter APIs
150154

151155
Docfx shows only the public accessible types and methods callable from another assembly. It also has a set of [default filtering rules](https://github.com/dotnet/docfx/blob/main/src/Docfx.Dotnet/Resources/defaultfilterconfig.yml) that excludes common API patterns based on attributes such as `[EditorBrowsableAttribute]`.
@@ -154,45 +158,18 @@ To disable the default filtering rules, set the `disableDefaultFilter` property
154158

155159
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.
156160

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

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

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" />
165+
```csharp
166+
/// <exclude />
167+
public class Foo { }
167168
```
168169

169-
2. Configure the filter options:
170+
### Custom filter rules
170171

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);
182-
```
183-
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
194-
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:
172+
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:
196173

197174
```json
198175
{
@@ -265,3 +242,38 @@ apiRules:
265242
```
266243

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

0 commit comments

Comments
 (0)