Skip to content

Commit

Permalink
Properties and method from inheritance chain. Making it work with ext…
Browse files Browse the repository at this point in the history
…ensions.
  • Loading branch information
tesar-tech committed Nov 22, 2024
1 parent 58113e6 commit 6892987
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</DocsPageSubtitle>

@{
IEnumerable<ApiDocsForComponent> apiDocsForComponents = ComponentTypes.Select(x => ComponentsApiDocsSource.Components.GetValueOrDefault(x)).Where(x => x is not null);
IEnumerable<ApiDocsForComponent> apiDocsForComponents = ComponentTypes.Select(x => ComponentsApiDocsSource.Instance.Components.GetValueOrDefault(x)).Where(x => x is not null);
var hasComponentTypes = ComponentTypes.Any();
var multipleComponentTypes = ComponentTypes.Count > 1;
var hasMethods = apiDocsForComponents.Any(x => x.Methods.Any());
Expand Down Expand Up @@ -58,7 +58,7 @@
@foreach (ApiDocsForComponentMethod method in apiDocsForComponent.Methods)
{
<DocsMethodsItem Name="@method.Name" ReturnType="@method.ReturnTypeName" Parameters="@(string.Join(", ",method.Parameters.Select(x=>$"{x.TypeName} {x.Name}") ))">
@method.Description
@((MarkupString)method.Description)
</DocsMethodsItem>
}
</DocsMethods>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
When working with email and password fields, in some cases browsers might automatically autofill them with the values from user system. To prevent it you can define an <Code>autocomplete</Code> attribute, eg. <Code>autocomplete="new-password"</Code> on an input field.
</DocsPageParagraph>

<ComponentApiDocs ComponentTypes="[typeof(TextEdit), typeof(BaseTextInput<>)]" />
<ComponentApiDocs ComponentTypes="[typeof(TextEdit)]" />

<DocsPageSubtitle>
API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
<DocsPageSectionSource Code="SnackbarStackedExample" />
</DocsPageSection>

<ComponentApiDocs ComponentTypes="[typeof(Snackbar)]" />


<DocsPageSubtitle>
API
</DocsPageSubtitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

<ItemGroup>
<ProjectReference Include="..\..\Blazorise\Blazorise.csproj" />
<ProjectReference Include="..\..\SourceGenerators\Blazorise.ApiDocsGenerator\Blazorise.ApiDocsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" PrivateAssets="all" />
<ProjectReference Include="..\..\SourceGenerators\Blazorise.ApiDocsDtos\Blazorise.ApiDocsDtos.csproj" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
66 changes: 61 additions & 5 deletions Source/SourceGenerators/Blazorise.ApiDocsDtos/ApiDocsDtos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,73 @@ namespace Blazorise;
/// This almost keeps parity with ApiDocsDtos in sg project.
public class ApiDocsForComponent
{
public ApiDocsForComponent( Type type,string typeName, List<ApiDocsForComponentProperty> properties, List<ApiDocsForComponentMethod> methods )
public ApiDocsForComponent( Type type,string typeName, List<ApiDocsForComponentProperty> properties, List<ApiDocsForComponentMethod> methods, List<Type> inheritsFromChain)
{
Type = type;
TypeName = typeName;
Properties = properties;
Methods = methods;
OwnProperties = properties;
OwnMethods = methods;
InheritsFromChain = inheritsFromChain;
}
public Type Type { get; set; }
public string TypeName { get; }
public List<ApiDocsForComponentProperty> Properties { get; set; }
public List<ApiDocsForComponentMethod> Methods { get; set; }

/// <summary>
/// properties form the component and its parents
/// </summary>
public List<ApiDocsForComponentProperty> Properties
{
get
{
if(_properties!=null) return _properties;
_properties = new List<ApiDocsForComponentProperty>();
_properties.AddRange(OwnProperties);
foreach ( Type typeInheritFrom in InheritsFromChain )
{
bool success = ComponentsApiDocsSource.Instance.Components.TryGetValue(typeInheritFrom, out var component);
if(!success || component is null) continue;
_properties.AddRange(component.OwnProperties);
}
return _properties;
}
}

private List<ApiDocsForComponentProperty>? _properties;

/// <summary>
/// only properties from the component, not from parents
/// </summary>
public List<ApiDocsForComponentProperty> OwnProperties { get; set; }
/// <summary>
/// Methods from the component and its parents
/// </summary>
public List<ApiDocsForComponentMethod> Methods
{
get
{
if (_methods != null) return _methods;
_methods = new List<ApiDocsForComponentMethod>();
_methods.AddRange(OwnMethods);
foreach (Type typeInheritFrom in InheritsFromChain)
{
bool success = ComponentsApiDocsSource.Instance.Components.TryGetValue(typeInheritFrom, out var component);
if (!success || component is null) continue;
_methods.AddRange(component.OwnMethods);
}
return _methods;
}
}

private List<ApiDocsForComponentMethod>? _methods;

/// <summary>
/// Only methods from the component, not from parents
/// </summary>
public List<ApiDocsForComponentMethod> OwnMethods { get; set; }


//chain of inherited classes from component to BaseComponent
public List<Type> InheritsFromChain { get; set; }

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
</PropertyGroup>

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Blazorise;


public interface IComponentsApiDocsSource
{
Dictionary<Type, ApiDocsForComponent> Components { get; }
}

public class ComponentsApiDocsSource
{
public Dictionary<Type, ApiDocsForComponent> Components { get; }

private static readonly Lazy<ComponentsApiDocsSource> instance = new(() => new ComponentsApiDocsSource());
public static ComponentsApiDocsSource Instance => instance.Value;

private ComponentsApiDocsSource()
{
Components = new Dictionary<Type, ApiDocsForComponent>();

// Find all types implementing IComponentsApiDocsSource
var sources = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => typeof(IComponentsApiDocsSource).IsAssignableFrom(type) && type is { IsInterface: false, IsAbstract: false });

foreach (var sourceType in sources)
{
// Create an instance of the source type
if ( Activator.CreateInstance( sourceType ) is not IComponentsApiDocsSource sourceInstance )
continue;
// Merge Components dictionary
foreach (var component in sourceInstance.Components)
{
if (!Components.ContainsKey(component.Key))
{
Components[component.Key] = component.Value;
}
else
{
// Handle duplicates if needed, e.g., log or overwrite
}
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,52 @@

namespace Blazorise.ApiDocsGenerator;



[Generator]
public class ComponentsApiDocsGenerator : IIncrementalGenerator
{
public void Initialize( IncrementalGeneratorInitializationContext context )
{
var componentProperties = context.CompilationProvider
.Select( ( compilation, _ ) => ( compilation, components: GetComponentProperties( compilation ).ToImmutableArray() ) );
.Select( ( compilation, _ ) => ( compilation, components: GetComponentProperties( compilation,GetNamespaceToSearch( compilation) ).ToImmutableArray() ) );



context.RegisterSourceOutput( componentProperties, ( ctx, source ) =>
{
Logger.LogAlways( DateTime.Now.ToLongTimeString() );
var (compilation, components) = source;
var sourceText = GenerateComponentsApiSource( compilation, components );
INamespaceSymbol namespaceToSearch = GetNamespaceToSearch( compilation);
var sourceText = GenerateComponentsApiSource( compilation, components, namespaceToSearch );
ctx.AddSource( "ComponentsApiSource.g.cs", SourceText.From( sourceText, Encoding.UTF8 ) );
ctx.AddSource( "Log.txt", SourceText.From( Logger.LogMessages, Encoding.UTF8 ) );
} );
}

private static IEnumerable<(INamedTypeSymbol ComponentType, IEnumerable<IPropertySymbol> Properties, IEnumerable<IMethodSymbol> Methods)> GetComponentProperties( Compilation compilation )
private INamespaceSymbol GetNamespaceToSearch( Compilation compilation )
{
var blazoriseNamespace = compilation.GlobalNamespace
.GetNamespaceMembers()
.FirstOrDefault( ns => ns.Name == $"Blazorise" );


if ( blazoriseNamespace is null ) return null;

var namespaceToSearch = compilation.Assembly.Name == "Blazorise" ? blazoriseNamespace
: blazoriseNamespace.GetNamespaceMembers().FirstOrDefault( ns => ns.Name == compilation.Assembly.Name.Split( '.' ).Last() );

return namespaceToSearch;

}

private static IEnumerable< FoundComponent> GetComponentProperties( Compilation compilation, INamespaceSymbol namespaceToSearch )
{

Logger.LogAlways( $"Local Namespace:{compilation.Assembly.Name} " );

var parameterAttributeSymbol = compilation.GetTypeByMetadataName( "Microsoft.AspNetCore.Components.ParameterAttribute" );
if ( parameterAttributeSymbol == null )
yield break;
Expand All @@ -44,17 +65,20 @@ public void Initialize( IncrementalGeneratorInitializationContext context )
if ( baseComponentSymbol == null )
yield break;

var blazoriseNamespace = compilation.GlobalNamespace
.GetNamespaceMembers()
.FirstOrDefault( ns => ns.Name == "Blazorise" );

if ( blazoriseNamespace is null )

Logger.LogAlways( $"To look for {namespaceToSearch} " );
if ( namespaceToSearch is null )
yield break;

foreach ( var type in blazoriseNamespace.GetTypeMembers().OfType<INamedTypeSymbol>() )
foreach ( var type in namespaceToSearch.GetTypeMembers().OfType<INamedTypeSymbol>() )
{
if ( type.TypeKind != TypeKind.Class || !InheritsFrom( type, baseComponentSymbol ) )

if ( type.TypeKind != TypeKind.Class )
continue;
var (inheritsFromBaseComponent, inheritsFromChain) = InheritsFrom( type, baseComponentSymbol ) ;
if(!inheritsFromBaseComponent) continue;

Logger.LogAlways( $"Type {type.Name} base {type.BaseType}" );
var parameterProperties = type
.GetMembers()
.OfType<IPropertySymbol>()
Expand All @@ -70,45 +94,52 @@ public void Initialize( IncrementalGeneratorInitializationContext context )
m.MethodKind == MethodKind.Ordinary );


yield return ( type, parameterProperties, publicMethods );
yield return new FoundComponent
(
Type : type,
PublicMethods : publicMethods,
Properties : parameterProperties,
InheritsFromChain : inheritsFromChain
);
}
}

private static bool InheritsFrom( INamedTypeSymbol type, INamedTypeSymbol baseType )
private static (bool, IEnumerable<INamedTypeSymbol>) InheritsFrom( INamedTypeSymbol type, INamedTypeSymbol baseType )
{
List<INamedTypeSymbol> inheritsFromChain = [];
while ( type != null )
{
if ( SymbolEqualityComparer.Default.Equals( type.BaseType, baseType ) )
return true;
return ( true, inheritsFromChain );

type = type.BaseType;
inheritsFromChain.Add( type );
}
return false;
return ( false, [] );
}

private static string GenerateComponentsApiSource( Compilation compilation, ImmutableArray<(INamedTypeSymbol ComponentType, IEnumerable<IPropertySymbol> Properties, IEnumerable<IMethodSymbol> Methods)> components )

private static string GenerateComponentsApiSource( Compilation compilation, ImmutableArray<FoundComponent> components, INamespaceSymbol namespaceToSearch )
{

Logger.LogAlways( components.Count().ToString() );
IEnumerable<ApiDocsForComponent> componentsData = components.Select( component =>
{
// var componentType = component.ComponentType;
string componentType = component.ComponentType.ToStringWithGenerics();
string componentTypeName = OtherHelpers.GetSimplifiedTypeName( component.ComponentType );
Logger.IsOn = component.ComponentType.Name == "Button";
Logger.Log( component.ComponentType.Name );
string componentType = component.Type.ToStringWithGenerics();
string componentTypeName = OtherHelpers.GetSimplifiedTypeName( component.Type );
Logger.IsOn = component.Type.Name == "Button";
Logger.Log( component.Type.Name );
var propertiesData = component.Properties.Select( property =>
InfoExtractor.GetPropertyDetails( compilation, property ) );
if ( component.Methods.Any() )
Logger.LogAlways( $"{component.Methods.Count()} commp . {component.ComponentType.Name}" );
var methodsData = component.Methods.Select( method =>
var methodsData = component.PublicMethods.Select( method =>
InfoExtractor.GetMethodDetails( compilation, method ) );
ApiDocsForComponent comp = new(type: componentType, typeName: componentTypeName,
properties: propertiesData, methods: methodsData);
properties: propertiesData, methods: methodsData,
inheritsFromChain: component.InheritsFromChain.Select( type => type.ToStringWithGenerics() ) );
return comp;
} );

Expand All @@ -123,9 +154,10 @@ private static string GenerateComponentsApiSource( Compilation compilation, Immu

namespace Blazorise.Docs;

public static class ComponentsApiDocsSource
public class ComponentApiSource_ForNamespace_{{namespaceToSearch.Name}}:IComponentsApiDocsSource
{
public static readonly Dictionary<Type, ApiDocsForComponent> Components = new Dictionary<Type, ApiDocsForComponent>
public Dictionary<Type, ApiDocsForComponent> Components { get; } =
new Dictionary<Type, ApiDocsForComponent>
{
{{componentsData.Where( comp => comp is not null ).Select( comp =>
{
Expand All @@ -138,7 +170,7 @@ public static class ComponentsApiDocsSource
$"""
new ("{prop.Name}",typeof({prop.Type}), "{prop.TypeName}", {prop.DefaultValue},{prop.DefaultValueString}, "{prop.Description}", {( prop.IsBlazoriseEnum ? "true" : "false" )}),
""" ).StringJoin( "\n" )
""" ).StringJoin( " " )
}}},
new List<ApiDocsForComponentMethod>{
{{
Expand All @@ -152,12 +184,14 @@ public static class ComponentsApiDocsSource
$"""
new ("{param.Name}","{param.TypeName}" ),
"""
).StringJoin( "\n" )
).StringJoin( " " )
}} }),
""" ).StringJoin( "\n" )
}}
}
""" ).StringJoin( " " )
}}
},
new List<Type>{
{{comp.InheritsFromChain.Select(x=> $"typeof({x})").StringJoin(",") }}
}
)},
""";
Expand All @@ -167,4 +201,12 @@ public static class ComponentsApiDocsSource
}
""";
}
}

public record FoundComponent( INamedTypeSymbol Type, IEnumerable<IPropertySymbol> Properties, IEnumerable<IMethodSymbol> PublicMethods, IEnumerable<INamedTypeSymbol> InheritsFromChain )
{
public INamedTypeSymbol Type { get; } = Type;
public IEnumerable<IPropertySymbol> Properties { get; } = Properties;
public IEnumerable<IMethodSymbol> PublicMethods { get; } = PublicMethods;
public IEnumerable<INamedTypeSymbol> InheritsFromChain { get; } = InheritsFromChain;
}
Loading

0 comments on commit 6892987

Please sign in to comment.