Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>

<ItemGroup>
<ProjectReference
Include="..\..\..\Controls\src\Core\Controls.SourceGen.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\BlazorWebView\samples\MauiRazorClassLibrarySample\MauiRazorClassLibrarySample.csproj" />
</ItemGroup>
Expand Down
43 changes: 30 additions & 13 deletions src/Controls/src/SourceGen/CodeBehindGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -44,20 +45,25 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
var xmlnsDefinitionsProvider = initContext.CompilationProvider
.Select(GetAssemblyAttributes);

var typeCacheProvider = initContext.CompilationProvider
.Select(GetTypeCache);

var sourceProvider = projectItemProvider
.Combine(xmlnsDefinitionsProvider)
.Combine(typeCacheProvider)
.Combine(initContext.CompilationProvider)
.Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right));

initContext.RegisterSourceOutput(sourceProvider, static (sourceProductionContext, provider) =>
{
var (projectItem, caches, compilation) = provider;
var ((projectItem, xmlnsCache), typeCache, compilation) = provider;
if (projectItem == null)
return;

switch (projectItem.Kind)
{
case "Xaml":
GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, caches);
GenerateXamlCodeBehind(projectItem, compilation, sourceProductionContext, xmlnsCache, typeCache);
break;
case "Css":
GenerateCssCodeBehind(projectItem, sourceProductionContext);
Expand Down Expand Up @@ -137,7 +143,12 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio
return new AssemblyCaches(xmlnsDefinitions, internalsVisible);
}

static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches)
static IDictionary<XmlType, string> GetTypeCache(Compilation compilation, CancellationToken cancellationToken)
{
return new Dictionary<XmlType, string>();
Comment thread
mgoertz-msft marked this conversation as resolved.
}

static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation, SourceProductionContext context, AssemblyCaches caches, IDictionary<XmlType, string> typeCache)
{
var text = projItem.AdditionalText.GetText(context.CancellationToken);
if (text == null)
Expand All @@ -149,7 +160,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation
return;
var uid = Crc64.ComputeHashString($"{compilation.AssemblyName}.{itemName}");

if (!TryParseXaml(text, uid, compilation, caches, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException))
if (!TryParseXaml(text, uid, compilation, caches, typeCache, context.CancellationToken, projItem.TargetFramework, out var accessModifier, out var rootType, out var rootClrNamespace, out var generateDefaultCtor, out var addXamlCompilationAttribute, out var hideFromIntellisense, out var XamlResourceIdOnly, out var baseType, out var namedFields, out var parseException))
{
if (parseException != null)
{
Expand Down Expand Up @@ -237,7 +248,7 @@ static void GenerateXamlCodeBehind(ProjectItem projItem, Compilation compilation
context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8));
}

static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception)
static bool TryParseXaml(SourceText text, string uid, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken, string? targetFramework, out string? accessModifier, out string? rootType, out string? rootClrNamespace, out bool generateDefaultCtor, out bool addXamlCompilationAttribute, out bool hideFromIntellisense, out bool xamlResourceIdOnly, out string? baseType, out IEnumerable<(string, string, string)>? namedFields, out Exception? exception)
{
cancellationToken.ThrowIfCancellationRequested();

Expand Down Expand Up @@ -320,9 +331,9 @@ static bool TryParseXaml(SourceText text, string uid, Compilation compilation, A
return true;
}

namedFields = GetNamedFields(root, nsmgr, compilation, caches, cancellationToken);
namedFields = GetNamedFields(root, nsmgr, compilation, caches, typeCache, cancellationToken);
var typeArguments = GetAttributeValue(root, "TypeArguments", XamlParser.X2006Uri, XamlParser.X2009Uri);
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches);
baseType = GetTypeName(new XmlType(root.NamespaceURI, root.LocalName, typeArguments != null ? TypeArgumentsParser.ParseExpression(typeArguments, nsmgr, null) : null), compilation, caches, typeCache);

// x:ClassModifier attribute
var classModifier = GetAttributeValue(root, "ClassModifier", XamlParser.X2006Uri, XamlParser.X2009Uri);
Expand All @@ -345,7 +356,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
return true;
}

static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, CancellationToken cancellationToken)
static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache, CancellationToken cancellationToken)
{
var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri);
if (xPrefix == null)
Expand All @@ -371,13 +382,17 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc)
var accessModifier = fieldModifier?.ToLowerInvariant().Replace("notpublic", "internal") ?? "private"; //notpublic is WPF for internal
if (!new[] { "private", "public", "internal", "protected" }.Contains(accessModifier)) //quick validation
accessModifier = "private";
yield return (name ?? "", GetTypeName(xmlType, compilation, caches), accessModifier);
yield return (name ?? "", GetTypeName(xmlType, compilation, caches, typeCache), accessModifier);
}
}

static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches)
static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches caches, IDictionary<XmlType, string> typeCache)
{
string returnType;
if (typeCache.TryGetValue(xmlType, out string returnType))
{
return returnType;
}

var ns = GetClrNamespace(xmlType.NamespaceUri);
if (ns != null)
returnType = $"{ns}.{xmlType.Name}";
Expand All @@ -388,9 +403,11 @@ static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCach
}

if (xmlType.TypeArguments != null)
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches)))}>";
returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, caches, typeCache)))}>";

return $"global::{returnType}";
returnType = $"global::{returnType}";
typeCache[xmlType] = returnType;
return returnType;
}

static string? GetClrNamespace(string namespaceuri)
Expand Down
8 changes: 8 additions & 0 deletions src/Controls/src/SourceGen/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we want to check in ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think we do. It provides a simple debug target to debug the source generator.

"SourceGen - Maui.Controls.Sample": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\..\\samples\\Controls.Sample\\Maui.Controls.Sample.csproj"
}
}
}
29 changes: 29 additions & 0 deletions src/Controls/src/Xaml/XamlNode.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
Expand Down Expand Up @@ -53,6 +54,34 @@ public XmlType(string namespaceUri, string name, IList<XmlType> typeArguments)
public string NamespaceUri { get; }
public string Name { get; }
public IList<XmlType> TypeArguments { get; }

public override bool Equals(object obj)
{
if (obj is not XmlType other)
{
return false;
}

return
NamespaceUri == other.NamespaceUri &&
Name == other.Name &&
(TypeArguments == null && other.TypeArguments == null || TypeArguments.SequenceEqual(other.TypeArguments));
}

public override int GetHashCode()
{
unchecked
{
#if NETSTANDARD2_0
int hashCode = NamespaceUri.GetHashCode();
hashCode = (hashCode * 397) ^ Name.GetHashCode();
#else
int hashCode = NamespaceUri.GetHashCode(StringComparison.Ordinal);
hashCode = (hashCode * 397) ^ Name.GetHashCode(StringComparison.Ordinal);
#endif
return hashCode;
}
}
}

abstract class BaseNode : IXmlLineInfo, INode
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Xaml/XmlTypeXamlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ static class XmlTypeXamlExtensions
lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns) { AssemblyName = asmstring });
}

var lookupNames = new List<string>();
var lookupNames = new List<string>(capacity: 2);
if (elementName != "DataTemplate" && !elementName.EndsWith("Extension", StringComparison.Ordinal))
lookupNames.Add(elementName + "Extension");
lookupNames.Add(elementName);
Expand Down