Skip to content

Commit

Permalink
Merge 554c3db into 64ba963
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeih authored Nov 29, 2023
2 parents 64ba963 + 554c3db commit 6d9fd19
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 52 deletions.
80 changes: 46 additions & 34 deletions docs/docs/dotnet-api-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ To disable markdown parsing while processing XML tags, set `shouldSkipMarkup` to
}
```

In addition, docfx supports these C# documentation comments:

### `<exclude>`

## Filter APIs

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]`.
Expand All @@ -154,45 +158,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 `<exclude />` documentation comment

### Custom with Code
The `<exclude />` 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
<PackageReference Include="Docfx.Dotnet" Version="2.62.0" />
```csharp
/// <exclude />
public class Foo { }
```

2. Configure the filter options:
### Custom filter rules

```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.

### Custom with 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
{
Expand Down Expand Up @@ -265,3 +242,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
<PackageReference Include="Docfx.Dotnet" Version="2.62.0" />
```

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.
43 changes: 25 additions & 18 deletions src/Docfx.Dotnet/SymbolFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -127,4 +126,12 @@ bool IsEiiAndIncludesContainingSymbols(IEnumerable<ISymbol> symbols)
return symbols.Any() && symbols.All(s => IncludeApi(s.ContainingSymbol));
}
}

private static bool HasExcludeDocumentComment(ISymbol symbol)
{
return symbol.GetDocumentationCommentXml() is { } xml && (
xml.Contains("<exclude/>") ||
xml.Contains("<exclude>") ||
xml.Contains("<exclude "));
}
}
21 changes: 21 additions & 0 deletions test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3709,4 +3709,25 @@ public interface IFoo { void Bar(); }
Assert.Equal("public class Foo : IFoo", foo.Syntax.Content[SyntaxLanguage.CSharp]);
Assert.Equal("void IFoo.Bar()", foo.Items[0].Syntax.Content[SyntaxLanguage.CSharp]);
}

[Fact]
public void TestExcludeDocumentationComment()
{
var code =
"""
namespace Test
{
public class Foo
{
/// <exclude />
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);
}
}

0 comments on commit 6d9fd19

Please sign in to comment.