-
-
Notifications
You must be signed in to change notification settings - Fork 532
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Docs: ApiDocsGenerator for API documentation from XML comments (#5846)
* ApiDocsGenerator * Adding simple logger that emits logs into cs file. * Handles default values. * adds README.md * Enum values are part of the comment. * Handling multiple components in one docs page * small edits * Parametrize the Name -> column in table for API * Methods for APIdocs done! * Styling adjustments * Fix docs api structure * Use shared generator settings * Move apidocs dtos to own project. * Adds typeName for components (mainly bcs of generics). * Generator also includes generic types (e.g. BaseTextInput<TValue>) * Properties and method from inheritance chain. Making it work with extensions. * inheritdocs, "should only be used internally" * Add ApiDocsDtos to solution * Don't show empty parameters * checked some of the docs pages * prepare to be checked * xml comment improvements * Update apidocsGenerator with Remarks and `<c>` support * Formating * Improve BaseComponent comments * markup string on remarks * Better Should only be used internally message * Improve Field comments * Adds Events to the apidocs. * Replaces base component type name inside description or remarks with component type name * Rename "description" to "summary". * removes dead code * typo fix * support for <seealso (links in xml comments), fixes generic param in <see. New XmlCommentToHtmlConverter.cs (to keep the parsing stuff in one place). * (D-V) new api docs on several components * BreadcrumbItem comment * format * see instead of seealso * Render content of see tag. * cleanups * removes redundant type (on enums and enum like types) * Blazorise constants with numeric values. Fix for enums from different namespaces (e.g. Snackbar) * Dont include DocsApiSourceGenerator to nuget * include fody. Blazorise dll cleanup works * ApiDocsGenerator and Dtos to existing Generators and Features projects; Fody project structure works as it should; Extensions.Snackbar with option to not use SGs on pack; * namespaces refactoring, small improvements * fixing for linux * Format csproj files * format md files * Readme update * remove IsPack, includes EnableApiDocsGenerator. * Converts [Generator] into analyzer that generates source files; Removes reference to generator from Blazorise.csproj;Works with generic better (BaseChar<> vs BaseChart<,,,>); Removes default value in "non-string" form (wasn't needed and caused troubles); Removes references to Generator.Features from Blazorise.Docs, moves the Dtos to Blazorise.Docs; Removes weaving of of ApiDocs from BlazoriseLib; * format csproj * clean, format, and reorganize * Formating * load only current assembly * Fix for generic in dictionary. * fix for component inheritance (up to IComponent) * fix from constant from complex type * Animate fix + docs * removes Logger.cs * Cleanups in ComponentApiDocs.razor. - review fixes. * Fix for retrieving xml comments from Microsoft.AspnetCore.Components namespace. * fix for !: inside the cref type * System.Runtime attempt; Default value to remove global:: * Extensions with new ApiDocs in place (also still with the old api for comparation) * fixing local index * Add missing comments * change local var name * More fixed comments * Skip internal components * Don't show enum values of enum has more than 30 values * Remove DataGrid SG API for now * Remove old apis * Remove old snackbar apis * Quick fix to not show Dispose method * Fix comments * support for generic type "rename". * rm assembly name inside defualt value string (Vide.SettingsList) * Unified place for type qualified for apidocs; support for other types then components (e.g. options) * Format * Cropper --------- Co-authored-by: Mladen Macanovic <[email protected]>
- Loading branch information
1 parent
0aeb89e
commit 4bba734
Showing
177 changed files
with
2,305 additions
and
4,686 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
310 changes: 310 additions & 0 deletions
310
Documentation/Blazorise.Docs.Compiler/ApiDocsGenerator/ComponentsApiDocsGenerator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,310 @@ | ||
#region Using directives | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Blazorise.Docs.Compiler.ApiDocsGenerator.Dtos; | ||
using Blazorise.Docs.Compiler.ApiDocsGenerator.Extensions; | ||
using Blazorise.Docs.Compiler.ApiDocsGenerator.Helpers; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
#endregion | ||
|
||
namespace Blazorise.Docs.Compiler.ApiDocsGenerator; | ||
|
||
public class ComponentsApiDocsGenerator | ||
{ | ||
#region Members | ||
|
||
private Assembly aspNetCoreComponentsAssembly; | ||
|
||
private CSharpCompilation blazoriseCompilation; | ||
|
||
private XmlDocumentationProvider aspnetCoreDocumentationProvider; | ||
|
||
|
||
private Assembly systemRuntimeAssembly; | ||
private XmlDocumentationProvider systemRuntimeDocumentationProvider; | ||
|
||
|
||
const string ShouldOnlyBeUsedInternally = "This method is intended for internal framework use only and should not be called directly by user code"; | ||
|
||
#endregion | ||
|
||
#region Constructors | ||
|
||
public ComponentsApiDocsGenerator() | ||
{ | ||
var aspnetCoreAssemblyName = typeof( Microsoft.AspNetCore.Components.ParameterAttribute ).Assembly.GetName().Name; | ||
aspNetCoreComponentsAssembly = AppDomain.CurrentDomain | ||
.GetAssemblies() | ||
.FirstOrDefault( a => a.GetName().Name == aspnetCoreAssemblyName ); | ||
|
||
systemRuntimeAssembly = AppDomain.CurrentDomain | ||
.GetAssemblies() | ||
.FirstOrDefault( a => a.GetName().Name == "System.Runtime" ); | ||
|
||
if ( systemRuntimeAssembly is not null ) | ||
{ | ||
systemRuntimeDocumentationProvider = XmlDocumentationProvider.CreateFromFile( $"{Path.GetFullPath( "." )}/System.Runtime.xml" ); | ||
} | ||
if ( aspNetCoreComponentsAssembly != null ) | ||
{ | ||
// Replace the .dll extension with .xml to get the documentation file path | ||
string xmlDocumentationPath = Path.ChangeExtension( aspNetCoreComponentsAssembly.Location, ".xml" ); | ||
aspnetCoreDocumentationProvider = XmlDocumentationProvider.CreateFromFile( xmlDocumentationPath ); | ||
} | ||
//get the blazorise compilation, it's needed for every extension. | ||
blazoriseCompilation = GetCompilation( Paths.BlazoriseLibRoot, "Blazorise", true ); | ||
} | ||
|
||
#endregion | ||
|
||
#region Methods | ||
|
||
public bool Execute() | ||
{ | ||
if ( aspNetCoreComponentsAssembly is null ) | ||
{ | ||
Console.WriteLine( $"Error generating ApiDocs. Cannot find ASP.NET Core assembly." ); | ||
return false; | ||
} | ||
if ( blazoriseCompilation is null ) | ||
{ | ||
Console.WriteLine( $"Error generating ApiDocs. Cannot find Blazorise assembly." ); | ||
return false; | ||
} | ||
if ( !Directory.Exists( Paths.BlazoriseExtensionsRoot ) ) | ||
{ | ||
Console.WriteLine( $"Directory for extensions does not exist: {Paths.BlazoriseExtensionsRoot}" ); | ||
return false; | ||
} | ||
|
||
//directories where to load the source code from one by one | ||
string[] inputLocations = [Paths.BlazoriseLibRoot, .. Directory.GetDirectories( Paths.BlazoriseExtensionsRoot )]; | ||
|
||
foreach ( var inputLocation in inputLocations ) | ||
{ | ||
string assemblyName = Path.GetFileName( inputLocation ); // Use directory name as assembly name | ||
|
||
CSharpCompilation compilation = inputLocation.EndsWith( "Blazorise" ) | ||
? blazoriseCompilation // the case for getting components from Blazorise | ||
: GetCompilation( inputLocation, assemblyName ); | ||
|
||
INamespaceSymbol namespaceToSearch = FindNamespace( compilation, assemblyName ); // e.g. Blazorise.Animate | ||
|
||
IEnumerable<ComponentInfo> componentInfo = GetComponentsInfo( compilation, namespaceToSearch ); | ||
string sourceText = GenerateComponentsApiSource( compilation, [.. componentInfo], assemblyName ); | ||
|
||
if ( !Directory.Exists( Paths.ApiDocsPath ) ) // BlazoriseDocs.ApiDocs | ||
Directory.CreateDirectory( Paths.ApiDocsPath ); | ||
|
||
string outputPath = Path.Join( Paths.ApiDocsPath, $"{assemblyName}.ApiDocs.cs" ); | ||
|
||
File.WriteAllText( outputPath, sourceText ); | ||
Console.WriteLine( $"API Docs generated for {assemblyName} at {outputPath}. {sourceText.Length} characters." ); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
//namespace are divided in chunks (Blazorise.Animate is under Blazorise...) | ||
INamespaceSymbol FindNamespace( Compilation compilation, string namespaceName, INamespaceSymbol? namespaceToSearch = null ) | ||
Check warning on line 114 in Documentation/Blazorise.Docs.Compiler/ApiDocsGenerator/ComponentsApiDocsGenerator.cs GitHub Actions / build
|
||
{ | ||
namespaceToSearch ??= compilation.GlobalNamespace | ||
.GetNamespaceMembers() | ||
.FirstOrDefault( ns => ns.Name == "Blazorise" ); | ||
|
||
if ( namespaceToSearch is null ) | ||
throw new Exception( $"Unable to find namespace {namespaceName}." ); | ||
|
||
if ( namespaceToSearch.ToDisplayString() == namespaceName ) | ||
return namespaceToSearch; | ||
|
||
foreach ( var childNamespace in namespaceToSearch.GetNamespaceMembers() ) | ||
{ | ||
var result = FindNamespace( compilation, namespaceName, childNamespace ); | ||
if ( result != null ) | ||
return result; | ||
} | ||
|
||
return null; | ||
} | ||
private CSharpCompilation GetCompilation( string inputLocation, string assemblyName, bool isBlazoriseAssembly = false ) | ||
{ | ||
var sourceFiles = Directory.GetFiles( inputLocation, "*.cs", SearchOption.AllDirectories ); | ||
|
||
List<MetadataReference> references = | ||
[ | ||
MetadataReference.CreateFromFile( systemRuntimeAssembly.Location, documentation:systemRuntimeDocumentationProvider ), // Microsoft.AspNetCore.Components | ||
MetadataReference.CreateFromFile( aspNetCoreComponentsAssembly.Location, documentation:aspnetCoreDocumentationProvider ), // Microsoft.AspNetCore.Components | ||
]; | ||
if ( !isBlazoriseAssembly ) //get Blazorise assembly as reference (for extensions) | ||
references.Add( blazoriseCompilation.ToMetadataReference() ); | ||
|
||
var syntaxTrees = sourceFiles.Select( file => CSharpSyntaxTree.ParseText( File.ReadAllText( file ) ) ); | ||
|
||
var compilation = CSharpCompilation.Create( | ||
assemblyName, | ||
syntaxTrees, | ||
references.ToImmutableArray(), | ||
new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) | ||
); | ||
return compilation; | ||
} | ||
|
||
private IEnumerable<ComponentInfo> GetComponentsInfo( Compilation compilation, INamespaceSymbol namespaceToSearch ) | ||
{ | ||
var baseComponentSymbol = compilation.GetTypeByMetadataName( "Blazorise.BaseComponent" ); | ||
|
||
foreach ( var type in namespaceToSearch.GetTypeMembers().OfType<INamedTypeSymbol>() ) | ||
{ | ||
var (qualifiesForApiDocs, inheritsFromChain, skipParamCheck) = QualifiesForApiDocs( type, baseComponentSymbol ); | ||
if ( !qualifiesForApiDocs ) | ||
continue; | ||
|
||
// Retrieve properties | ||
var parameterProperties = type.GetMembers() | ||
.OfType<IPropertySymbol>() | ||
.Where( p => | ||
p.DeclaredAccessibility == Accessibility.Public && | ||
( skipParamCheck || p.GetAttributes().Any( attr => | ||
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute" ) ) && | ||
p.OverriddenProperty == null ); | ||
|
||
// Retrieve methods | ||
var publicMethods = type.GetMembers() | ||
.OfType<IMethodSymbol>() | ||
.Where( m => m.DeclaredAccessibility == Accessibility.Public && | ||
!m.IsImplicitlyDeclared && | ||
m.MethodKind == MethodKind.Ordinary && | ||
m.OverriddenMethod == null ); | ||
|
||
yield return new ComponentInfo | ||
( | ||
Type: type, | ||
PublicMethods: publicMethods, | ||
Properties: parameterProperties, | ||
InheritsFromChain: inheritsFromChain | ||
); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// get the chain of inheritance to the BaseComponent or ComponentBase | ||
/// Only return true if implements IComponent (that is the case for all BaseComponent and ComponentBase) | ||
/// </summary> | ||
/// <param name="type"></param> | ||
/// <param name="baseType"></param> | ||
/// <returns></returns> | ||
private (bool qualifiesForApiDocs, IEnumerable<INamedTypeSymbol>, bool skipParamCheck) QualifiesForApiDocs( INamedTypeSymbol type, | ||
INamedTypeSymbol baseType ) | ||
{ | ||
|
||
(bool continueProcessing, bool skipParamAndComponentCheck) = type switch | ||
{ | ||
_ when type.TypeKind != TypeKind.Class || type.DeclaredAccessibility != Accessibility.Public => (false, false), | ||
_ when type.Name.StartsWith( '_' ) => (false, false), | ||
_ when type.Name.EndsWith( "Options" ) => (true, true), | ||
_ when type.Name.EndsWith( "RouterTabsPageAttribute" ) => (true, true), | ||
_ when !type.AllInterfaces.Any( i => i.Name == "IComponent" ) => (false, false), | ||
_ => (true, false) | ||
}; | ||
|
||
if ( !continueProcessing ) | ||
return (false, [], false); | ||
|
||
List<INamedTypeSymbol> inheritsFromChain = []; | ||
while ( type != null ) | ||
{ | ||
type = type.BaseType; | ||
if ( type?.Name.Split( "." ).Last() == "ComponentBase" //for this to work, the inheritance (:ComponentBase) must be specified in .cs file. | ||
|| SymbolEqualityComparer.Default.Equals( type, baseType ) | ||
) | ||
return (true, inheritsFromChain, skipParamAndComponentCheck); | ||
inheritsFromChain.Add( type ); | ||
} | ||
return (true, [], skipParamAndComponentCheck); | ||
} | ||
|
||
private static string GenerateComponentsApiSource( Compilation compilation, ImmutableArray<ComponentInfo> components, string assemblyName ) | ||
{ | ||
IEnumerable<ApiDocsForComponent> componentsData = components.Select( component => | ||
{ | ||
string componentType = component.Type.ToStringWithGenerics(); | ||
string componentTypeName = StringHelpers.GetSimplifiedTypeName( component.Type ); | ||
|
||
var propertiesData = component.Properties.Select( property => | ||
InfoExtractor.GetPropertyDetails( compilation, property ) ) | ||
.Where( x => !x.Summary.Contains( ShouldOnlyBeUsedInternally ) ); | ||
|
||
var methodsData = component.PublicMethods.Select( InfoExtractor.GetMethodDetails ) | ||
.Where( x => !x.Summary.Contains( ShouldOnlyBeUsedInternally ) ); | ||
|
||
ApiDocsForComponent comp = new( type: componentType, typeName: componentTypeName, | ||
properties: propertiesData, methods: methodsData, | ||
inheritsFromChain: component.InheritsFromChain.Select( type => type.ToStringWithGenerics() ) ); | ||
|
||
return comp; | ||
} ); | ||
|
||
return | ||
$$""" | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Generic; | ||
using System.Linq.Expressions; | ||
using System.Windows.Input; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Components.Forms; | ||
using Blazorise.Docs.Models.ApiDocsDtos; | ||
using Blazorise.Charts; | ||
namespace Blazorise.Docs.ApiDocs; | ||
public class ComponentApiSource_ForNamespace_{{assemblyName.Replace( ".", "_" )}}:IComponentsApiDocsSource | ||
{ | ||
public Dictionary<Type, ApiDocsForComponent> Components { get; } = | ||
new Dictionary<Type, ApiDocsForComponent> | ||
{ | ||
{{componentsData.Where( comp => comp is not null ).Select( comp => | ||
{ | ||
return $$""" | ||
{ typeof({{comp.Type}}),new ApiDocsForComponent(typeof({{comp.Type}}), | ||
"{{comp.TypeName}}", | ||
new List<ApiDocsForComponentProperty>{ | ||
{{comp.Properties.Select( prop => | ||
$""" | ||
new ("{prop.Name}",typeof({prop.Type}), "{prop.TypeName}",{prop.DefaultValueString}, "{prop.Summary}","{prop.Remarks}", {( prop.IsBlazoriseEnum ? "true" : "false" )}), | ||
""" ).StringJoin( " " )}}}, | ||
new List<ApiDocsForComponentMethod>{ | ||
{{comp.Methods.Select( method => | ||
$$""" | ||
new ("{{method.Name}}","{{method.ReturnTypeName}}", "{{method.Summary}}" ,"{{method.Remarks}}", | ||
new List<ApiDocsForComponentMethodParameter>{ | ||
{{method.Parameters.Select( param => | ||
$""" | ||
new ("{param.Name}","{param.TypeName}" ), | ||
""" | ||
).StringJoin( " " )}} }), | ||
""" ).StringJoin( " " )}} | ||
}, | ||
new List<Type>{ | ||
{{comp.InheritsFromChain.Select( x => $"typeof({x})" ).StringJoin( "," )}} | ||
} | ||
)}, | ||
"""; | ||
} | ||
).StringJoin( "\n" )}} | ||
}; | ||
} | ||
"""; | ||
} | ||
|
||
#endregion | ||
} |
32 changes: 32 additions & 0 deletions
32
Documentation/Blazorise.Docs.Compiler/ApiDocsGenerator/Dtos/ApiDocsForComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#region Using directives | ||
using System.Collections.Generic; | ||
#endregion | ||
|
||
namespace Blazorise.Docs.Compiler.ApiDocsGenerator.Dtos; | ||
|
||
/// <summary> | ||
/// Easier to gather necessary info. | ||
/// Almost keeps parity with Blazorise/Models/ApiDocsDtos.cs, changes here should be reflected there | ||
/// </summary> | ||
public class ApiDocsForComponent | ||
{ | ||
public ApiDocsForComponent( string type, string typeName, | ||
IEnumerable<ApiDocsForComponentProperty> properties, | ||
IEnumerable<ApiDocsForComponentMethod> methods, | ||
IEnumerable<string> inheritsFromChain ) | ||
{ | ||
Type = type; | ||
TypeName = typeName; | ||
Properties = properties; | ||
Methods = methods; | ||
InheritsFromChain = inheritsFromChain; | ||
} | ||
|
||
public string Type { get; } | ||
|
||
public string TypeName { get; } | ||
public IEnumerable<ApiDocsForComponentProperty> Properties { get; } | ||
public IEnumerable<ApiDocsForComponentMethod> Methods { get; } | ||
|
||
public IEnumerable<string> InheritsFromChain { get; } | ||
} |
Oops, something went wrong.