Skip to content

Commit

Permalink
Merge pull request #155 from Dreamescaper/generator_updates
Browse files Browse the repository at this point in the history
Some generator fixes
  • Loading branch information
Dreamescaper authored Oct 30, 2024
2 parents 1932978 + 8c4413e commit 26bd7f2
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>

<Description>Dotnet tool which generate BlazorBindings.Maui components for MAUI elements.</Description>
<PackAsTool>true</PackAsTool>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

using BlazorBindings.Maui.ComponentGenerator.Extensions;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
Expand Down Expand Up @@ -85,25 +82,29 @@ public partial class ComponentWrapperGenerator
var createNativeElement = isComponentAbstract ? "" : $@"
protected override {generatedType.GetTypeNameAndAddNamespace(typeToGenerate)} CreateNativeElement() => new();";

var handleParameter = !allProperties.Any() ? "" : $@"
protected override void HandleParameter(string name, object value)
{{
switch (name)
{{
{handleProperties.Trim()}
var handleParameter = !allProperties.Any() ? "" : $$"""
default:
base.HandleParameter(name, value);
break;
}}
}}";
protected override void HandleParameter(string name, object value)
{
switch (name)
{
{{handleProperties.Trim()}}
var renderAdditionalElementContent = !contentProperties.Any() ? "" : $@"
default:
base.HandleParameter(name, value);
break;
}
}
""";

protected override void RenderAdditionalElementContent({generatedType.GetTypeNameAndAddNamespace("Microsoft.AspNetCore.Components.Rendering", "RenderTreeBuilder")} builder, ref int sequence)
{{
base.RenderAdditionalElementContent(builder, ref sequence);{string.Concat(contentProperties.Select(prop => prop.RenderContentProperty()))}
}}";
var renderAdditionalElementContent = contentProperties.Length == 0 ? "" : $$"""
protected override void RenderAdditionalElementContent({{generatedType.GetTypeNameAndAddNamespace("Microsoft.AspNetCore.Components.Rendering", "RenderTreeBuilder")}} builder, ref int sequence)
{
base.RenderAdditionalElementContent(builder, ref sequence);{{string.Concat(contentProperties.Select(prop => prop.RenderContentProperty()))}}
}
""";


var usingsText = string.Join(
Expand All @@ -120,48 +121,55 @@ protected override void RenderAdditionalElementContent({generatedType.GetTypeNam

var xmlDoc = GetXmlDocContents(typeToGenerate, " ");

var content = $@"{headerText}
{usingsText}
#pragma warning disable MBB001
namespace {componentNamespace}
{{
{xmlDoc} public {classModifiers}partial class {componentName}{genericModifier} : {componentBaseName}{baseGenericModifier}
{{
static {componentName}()
{{
{staticConstructorBody.Trim()}
}}
{propertyDeclarations}
public new {mauiTypeName} NativeControl => ({mauiTypeName})((BindableObject)this).NativeControl;
{createNativeElement}
{handleParameter}{renderAdditionalElementContent}
static partial void RegisterAdditionalHandlers();
}}
}}
";
var content = $$"""
{{headerText}}
{{usingsText}}
#pragma warning disable MBB001
namespace {{componentNamespace}}
{
{{xmlDoc}} public {{classModifiers}}partial class {{componentName}}{{genericModifier}} : {{componentBaseName}}{{baseGenericModifier}}
{
static {{componentName}}()
{
{{staticConstructorBody.Trim()}}
}
{{propertyDeclarations}}
public new {{mauiTypeName}} NativeControl => ({{mauiTypeName}})((BindableObject)this).NativeControl;
{{createNativeElement}}
{{handleParameter}}{{renderAdditionalElementContent}}
static partial void RegisterAdditionalHandlers();
}
}
""";

return (GetComponentGroup(typeToGenerate), componentName, content);
}

private static List<UsingStatement> GetDefaultUsings(INamedTypeSymbol typeToGenerate, string componentNamespace)
{
var usings = new List<UsingStatement>
{
new UsingStatement { Namespace = "System" },
new UsingStatement { Namespace = "Microsoft.AspNetCore.Components", IsUsed = true, },
new UsingStatement { Namespace = "BlazorBindings.Core", IsUsed = true, },
new UsingStatement { Namespace = "System.Threading.Tasks", IsUsed = true, },
new UsingStatement { Namespace = "Microsoft.Maui.Controls", Alias = "MC", IsUsed = true },
new UsingStatement { Namespace = "Microsoft.Maui.Primitives", Alias = "MMP" }
};
{
new() { Namespace = "System" },
new() { Namespace = "Microsoft.AspNetCore.Components", IsUsed = true, },
new() { Namespace = "BlazorBindings.Core", IsUsed = true, },
new() { Namespace = "System.Threading.Tasks", IsUsed = true, },
new() { Namespace = "Microsoft.Maui.Controls", Alias = "MC", IsUsed = true },
new() { Namespace = "Microsoft.Maui.Primitives", Alias = "MMP" }
};

var existingAliases = usings
.Where(u => !string.IsNullOrEmpty(u.Alias))
.Select(u => u.Alias)
.ToHashSet();

var typeNamespace = typeToGenerate.ContainingNamespace.GetFullName();
if (typeNamespace != "Microsoft.Maui.Controls")
{
var typeNamespaceAlias = GetNamespaceAlias(typeNamespace);
var typeNamespaceAlias = GetUniqueNamespaceAlias(typeNamespace, existingAliases);
usings.Add(new UsingStatement { Namespace = typeNamespace, Alias = typeNamespaceAlias, IsUsed = true });
}

Expand All @@ -173,7 +181,7 @@ private static List<UsingStatement> GetDefaultUsings(INamedTypeSymbol typeToGene
var assemblyName = typeToGenerate.ContainingAssembly.Name;
if (assemblyName.Contains('.') && typeNamespace != assemblyName && typeNamespace.StartsWith(assemblyName))
{
usings.Add(new UsingStatement { Namespace = assemblyName, Alias = GetNamespaceAlias(assemblyName) });
usings.Add(new UsingStatement { Namespace = assemblyName, Alias = GetUniqueNamespaceAlias(assemblyName, existingAliases) });
}

return usings;
Expand Down Expand Up @@ -237,7 +245,7 @@ static string GetXmlDocText(XmlElement xmlDocElement)
{
var allText = xmlDocElement?.InnerXml;
allText = allText?.Replace("To be added.", "").Replace("This is a bindable property.", "");
allText = allText is null ? null : Regex.Replace(allText, @"\s+", " ");
allText = allText is null ? null : MultipleSpacesRegex().Replace(allText, " ");
return string.IsNullOrWhiteSpace(allText) ? null : allText.Trim();
}
}
Expand Down Expand Up @@ -271,10 +279,9 @@ public static string GetIdentifierName(string possibleIdentifier)
: possibleIdentifier;
}

private static readonly List<string> ReservedKeywords = new List<string>
{ "class", };
private static readonly List<string> ReservedKeywords = ["class"];

private string GetComponentGroup(INamedTypeSymbol typeToGenerate)
private static string GetComponentGroup(INamedTypeSymbol typeToGenerate)
{
var nsName = typeToGenerate.ContainingNamespace.GetFullName();
var parts = nsName.Split('.')
Expand All @@ -283,7 +290,7 @@ private string GetComponentGroup(INamedTypeSymbol typeToGenerate)
return string.Join('.', parts);
}

private string GetComponentNamespace(INamedTypeSymbol typeToGenerate)
private static string GetComponentNamespace(INamedTypeSymbol typeToGenerate)
{
var group = GetComponentGroup(typeToGenerate);
return string.IsNullOrEmpty(group) ? "BlazorBindings.Maui.Elements" : $"BlazorBindings.Maui.Elements.{group}";
Expand All @@ -293,4 +300,23 @@ private static string GetNamespaceAlias(string @namespace)
{
return new string(@namespace.Split('.').Where(part => part != "Microsoft").Select(part => part[0]).ToArray());
}
}

private static string GetUniqueNamespaceAlias(string @namespace, HashSet<string> existingAliases)
{
var baseAlias = GetNamespaceAlias(@namespace);
var uniqueAlias = baseAlias;
var counter = 1;

while (existingAliases.Contains(uniqueAlias))
{
uniqueAlias = baseAlias + counter;
counter++;
}

existingAliases.Add(uniqueAlias);
return uniqueAlias;
}

[GeneratedRegex(@"\s+")]
private static partial Regex MultipleSpacesRegex();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;

namespace BlazorBindings.Maui.ComponentGenerator.Extensions;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;

namespace BlazorBindings.Maui.ComponentGenerator;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using BlazorBindings.Maui.ComponentGenerator.Extensions;
using Microsoft.CodeAnalysis;
using System;
using System.Linq;
using System.Reflection;

namespace BlazorBindings.Maui.ComponentGenerator;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using BlazorBindings.Maui.ComponentGenerator.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Linq;

namespace BlazorBindings.Maui.ComponentGenerator;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using BlazorBindings.Maui.ComponentGenerator.Extensions;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace BlazorBindings.Maui.ComponentGenerator;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using BlazorBindings.Maui.ComponentGenerator.Extensions;
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BlazorBindings.Maui.ComponentGenerator;
Expand Down
55 changes: 24 additions & 31 deletions src/BlazorBindings.Maui.ComponentGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorBindings.Maui.ComponentGenerator;

public class Program
{
const string FileHeader = @"// <auto-generated>
// This code was generated by a BlazorBindings.Maui component generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
";
const string FileHeader = """
// <auto-generated>
// This code was generated by a BlazorBindings.Maui component generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
""";

public static async Task Main(string[] args)
{
Expand All @@ -31,18 +29,13 @@ await Parser.Default
.ParseArguments<Options>(args)
.WithParsedAsync(async o =>
{
if (o.ProjectPath is null)
{
o.ProjectPath = Directory.GetFiles(Directory.GetCurrentDirectory()).FirstOrDefault(f
=> f.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
?? throw new Exception("Cannot find any csproj files.");
}
if (o.OutPath is null)
{
o.OutPath = Path.Combine(o.ProjectPath, "..", "Elements");
}
o.ProjectPath ??= Directory.GetFiles(Directory.GetCurrentDirectory())
.FirstOrDefault(f => f.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
?? throw new ArgumentException("Cannot find any csproj files.");

o.OutPath ??= Path.Combine(o.ProjectPath, "..", "Elements");

var compilation = await CreateComplitation(o);
var compilation = await CreateCompilation(o);

var typesToGenerate = GetTypesToGenerate(compilation);

Expand Down Expand Up @@ -91,10 +84,10 @@ private static GenerateComponentSettings[] GetTypesToGenerate(Compilation compil
FileHeader = FileHeader,
TypeAlias = typeAlias,
TypeSymbol = typeSymbol,
Exclude = GetNamedArgumentValues(a, "Exclude").ToHashSet(),
Include = GetNamedArgumentValues(a, "Include").ToHashSet(),
ContentProperties = GetNamedArgumentValues(a, "ContentProperties").ToHashSet(),
NonContentProperties = GetNamedArgumentValues(a, "NonContentProperties").ToHashSet(),
Exclude = [.. GetNamedArgumentValues(a, "Exclude")],
Include = [.. GetNamedArgumentValues(a, "Include")],
ContentProperties = [.. GetNamedArgumentValues(a, "ContentProperties")],
NonContentProperties = [.. GetNamedArgumentValues(a, "NonContentProperties")],
PropertyChangedEvents = GetNamedArgumentValues(a, "PropertyChangedEvents"),
GenericProperties = GetNamedArgumentValues(a, "GenericProperties").Select(v => v.Split(':')).ToDictionary(v => v[0],
v => v.ElementAtOrDefault(1) is string genericArgName ? compilation.GetTypeByMetadataName(genericArgName) : null),
Expand Down Expand Up @@ -136,25 +129,25 @@ private static GenerateComponentSettings[] GetTypesToGenerate(Compilation compil
info.BaseTypeInfo = baseTypeInfo;
}

return typesToGenerate.ToArray();
return [.. typesToGenerate];
}

private static string[] GetNamedArgumentValues(AttributeData attribute, string name)
{
var argumentConstant = attribute.NamedArguments.FirstOrDefault(a => a.Key == name).Value;

if (argumentConstant.Kind != TypedConstantKind.Array)
return Array.Empty<string>();
return [];

var values = argumentConstant.Values;

if (values.IsDefaultOrEmpty)
return Array.Empty<string>();
return [];

return values.Select(a => a.Value as string).Where(v => v is not null).ToArray();
}

private static async Task<Compilation> CreateComplitation(Options o)
private static async Task<Compilation> CreateCompilation(Options o)
{
Console.WriteLine("Creating project compilation.");

Expand All @@ -165,7 +158,7 @@ private static async Task<Compilation> CreateComplitation(Options o)
return compilation;
}

private class Options
private sealed class Options
{
[Value(0, HelpText = "Project file path to run generator.")]
public string ProjectPath { get; set; }
Expand Down

0 comments on commit 26bd7f2

Please sign in to comment.