diff --git a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs index 9a1055ae3e8b..aa25464e32a7 100644 --- a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs +++ b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs @@ -25,19 +25,6 @@ public IEnumerable ConvertFromString(string value, ILContext contex yield return Instruction.Create(OpCodes.Ldsfld, bpRef); } - static bool IsOfAnyType(XmlType xmlType, params string[] types) - { - if (types == null || types.Length == 0) - return false; - if (xmlType == null) - return false; - if (xmlType.NamespaceUri != XamlParser.MauiUri && xmlType.NamespaceUri != XamlParser.MauiGlobalUri) - return false; - if (types.Contains(xmlType.Name)) - return true; - return false; - } - public FieldReference GetBindablePropertyFieldReference(string value, ILContext context, ModuleDefinition module, BaseNode node) { FieldReference bpRef = null; @@ -48,18 +35,18 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext if (parts.Length == 1) { var parent = node.Parent?.Parent as IElementNode ?? (node.Parent?.Parent as IListNode)?.Parent as IElementNode; - if (IsOfAnyType((node.Parent as ElementNode)?.XmlType, nameof(Setter), nameof(PropertyCondition))) + if ((node.Parent as ElementNode)?.XmlType is XmlType xt && xt.IsOfAnyType(nameof(Setter), nameof(PropertyCondition))) { - if (IsOfAnyType(parent.XmlType, nameof(Trigger), nameof(DataTrigger), nameof(MultiTrigger), nameof(Style))) + if (parent.XmlType.IsOfAnyType(nameof(Trigger), nameof(DataTrigger), nameof(MultiTrigger), nameof(Style))) { typeName = GetTargetTypeName(parent); } - else if (IsOfAnyType(parent.XmlType, nameof(VisualState))) + else if (parent.XmlType.IsOfAnyType(nameof(VisualState))) { typeName = FindTypeNameForVisualState(parent, node, context); } } - else if (IsOfAnyType((node.Parent as ElementNode)?.XmlType, nameof(Trigger))) + else if ((node.Parent as ElementNode)?.XmlType is XmlType xt1 && xt1.IsOfAnyType(nameof(Trigger))) { typeName = GetTargetTypeName(node.Parent); } @@ -99,18 +86,18 @@ static XmlType FindTypeNameForVisualState(IElementNode parent, IXmlLineInfo line //2. check that the VS is in a VSG // if (!(parent.Parent is IElementNode target) || target.XmlType.NamespaceUri != XamlParser.MauiUri || target.XmlType.Name != nameof(VisualStateGroup)) - if (!(parent.Parent is IElementNode target) || !IsOfAnyType(target.XmlType, nameof(VisualStateGroup))) + if (!(parent.Parent is IElementNode target) || !target.XmlType.IsOfAnyType(nameof(VisualStateGroup))) throw new XamlParseException($"Expected {nameof(VisualStateGroup)} but found {parent.Parent}", lineInfo); //3. if the VSG is in a VSGL, skip that as it could be implicit - if (target.Parent is ListNode - || IsOfAnyType((target.Parent as IElementNode)?.XmlType, nameof(VisualStateGroupList))) + if ( target.Parent is ListNode + || target.Parent is IElementNode { XmlType: XmlType xt } && xt.IsOfAnyType(nameof(VisualStateGroupList))) target = target.Parent.Parent as IElementNode; else target = target.Parent as IElementNode; //4. target is now a Setter in a Style, or a VE - if (IsOfAnyType(target.XmlType, nameof(Setter))) + if (target.XmlType.IsOfAnyType(nameof(Setter))) { var targetType = ((target?.Parent as IElementNode)?.Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string; return TypeArgumentsParser.ParseSingle(targetType, parent.NamespaceResolver, lineInfo); @@ -121,8 +108,7 @@ static XmlType FindTypeNameForVisualState(IElementNode parent, IXmlLineInfo line public static FieldReference GetBindablePropertyFieldReference(XamlCache cache, TypeReference typeRef, string propertyName, ModuleDefinition module) { - TypeReference declaringTypeReference; - FieldReference bpRef = typeRef.GetField(cache, fd => fd.Name == $"{propertyName}Property" && fd.IsStatic && fd.IsPublic, out declaringTypeReference); + FieldReference bpRef = typeRef.GetField(cache, fd => fd.Name == $"{propertyName}Property" && fd.IsStatic && fd.IsPublic, out TypeReference declaringTypeReference); if (bpRef != null) { bpRef = module.ImportReference(bpRef.ResolveGenericParameters(declaringTypeReference)); diff --git a/src/Controls/src/SourceGen/Controls.SourceGen.csproj b/src/Controls/src/SourceGen/Controls.SourceGen.csproj index df297d5414f4..321236c3ec2e 100644 --- a/src/Controls/src/SourceGen/Controls.SourceGen.csproj +++ b/src/Controls/src/SourceGen/Controls.SourceGen.csproj @@ -33,6 +33,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/src/Controls/src/SourceGen/InitializeComponentCodeWriter.cs b/src/Controls/src/SourceGen/InitializeComponentCodeWriter.cs index db325e2eeae7..ae3205cf2916 100644 --- a/src/Controls/src/SourceGen/InitializeComponentCodeWriter.cs +++ b/src/Controls/src/SourceGen/InitializeComponentCodeWriter.cs @@ -91,6 +91,7 @@ PrePost newblock() => FilePath = xamlItem.ProjectItem.RelativePath, EnableLineInfo = xamlItem.ProjectItem.EnableLineInfo, EnableDiagnostics = xamlItem.ProjectItem.EnableDiagnostics, + TargetFramework = xamlItem.ProjectItem.TargetFramework ?? "", }; using (newblock()) { @@ -123,6 +124,8 @@ static void Visit(RootNode rootnode, SourceGenContext visitorContext, bool useDe if (useDesignProperties) rootnode.Accept(new RemoveDuplicateDesignNodes(), null); rootnode.Accept(new SimplifyTypeExtensionVisitor(), null); + if (!string.IsNullOrEmpty(visitorContext.TargetFramework)) + rootnode.Accept(new SimplifyOnPlatformVisitor(visitorContext.TargetFramework), null); rootnode.Accept(new CreateValuesVisitor(visitorContext), null); rootnode.Accept(new SetNamescopesAndRegisterNamesVisitor(visitorContext), null); //set namescopes for {x:Reference} and FindByName rootnode.Accept(new SetFieldsForXNamesVisitor(visitorContext), null); diff --git a/src/Controls/src/SourceGen/NodeSGExtensions.cs b/src/Controls/src/SourceGen/NodeSGExtensions.cs index abf05b941c33..48db6027d581 100644 --- a/src/Controls/src/SourceGen/NodeSGExtensions.cs +++ b/src/Controls/src/SourceGen/NodeSGExtensions.cs @@ -551,19 +551,6 @@ public static void RegisterSourceInfo(this INode node, SourceGenContext context, writer.Indent--; } - static bool IsOfAnyType(XmlType xmlType, params string[] types) - { - if (types == null || types.Length == 0) - return false; - if (xmlType == null) - return false; - if (xmlType.NamespaceUri != XamlParser.MauiUri && xmlType.NamespaceUri != XamlParser.MauiGlobalUri) - return false; - if (types.Contains(xmlType.Name)) - return true; - return false; - } - public static IFieldSymbol GetBindableProperty(this ValueNode node, SourceGenContext context) { static ITypeSymbol? GetTargetTypeSymbol(INode node, SourceGenContext context) @@ -584,14 +571,14 @@ public static IFieldSymbol GetBindableProperty(this ValueNode node, SourceGenCon { ITypeSymbol? typeSymbol = null; var parent = node.Parent?.Parent as IElementNode ?? (node.Parent?.Parent as IListNode)?.Parent as IElementNode; - if (IsOfAnyType((node.Parent as ElementNode)?.XmlType!, "Setter", "PropertyCondition")) + if ((node.Parent as ElementNode)!.XmlType!.IsOfAnyType( "Setter", "PropertyCondition")) { - if (IsOfAnyType(parent!.XmlType, "Trigger", "DataTrigger", "MultiTrigger", "Style")) + if (parent!.XmlType.IsOfAnyType("Trigger", "DataTrigger", "MultiTrigger", "Style")) typeSymbol = GetTargetTypeSymbol(parent, context); - else if (IsOfAnyType(parent.XmlType, "VisualState")) + else if (parent.XmlType.IsOfAnyType("VisualState")) typeSymbol = FindTypeSymbolForVisualState(parent, context, node); } - else if (IsOfAnyType((node.Parent as ElementNode)?.XmlType!, "Trigger")) + else if ((node.Parent as ElementNode)!.XmlType!.IsOfAnyType("Trigger")) typeSymbol = GetTargetTypeSymbol(node.Parent!, context); var propertyName = parts[0]; @@ -615,19 +602,19 @@ public static IFieldSymbol GetBindableProperty(this ValueNode node, SourceGenCon //1. parent is VisualState, don't check that //2. check that the VS is in a VSG - if (!(parent.Parent is IElementNode target) || !IsOfAnyType(target.XmlType, "VisualStateGroup")) + if (!(parent.Parent is IElementNode target) || !target.XmlType.IsOfAnyType("VisualStateGroup")) throw new Exception($"Expected VisualStateGroup but found {parent.Parent}"); //3. if the VSG is in a VSGL, skip that as it could be implicit - if (target.Parent is ListNode - || IsOfAnyType((target.Parent as IElementNode)?.XmlType!, "VisualStateGroupList")) + if ( target.Parent is ListNode + || (target.Parent as IElementNode)!.XmlType!.IsOfAnyType( "VisualStateGroupList")) target = (IElementNode)target.Parent.Parent; else target = (IElementNode)target.Parent; XmlType? typeName = null; //4. target is now a Setter in a Style, or a VE - if (IsOfAnyType(target.XmlType, "Setter")) + if (target.XmlType.IsOfAnyType("Setter")) { var targetType = ((target?.Parent as IElementNode)?.Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string; typeName = TypeArgumentsParser.ParseSingle(targetType, parent.NamespaceResolver, lineInfo); @@ -639,7 +626,5 @@ public static IFieldSymbol GetBindableProperty(this ValueNode node, SourceGenCon } public static bool RepresentsType(this INode node, string namespaceUri, string name) - { - return node is IElementNode elementNode && elementNode.XmlType.RepresentsType(namespaceUri, name); - } + => node is IElementNode elementNode && elementNode.XmlType.RepresentsType(namespaceUri, name); } \ No newline at end of file diff --git a/src/Controls/src/SourceGen/SourceGenContext.cs b/src/Controls/src/SourceGen/SourceGenContext.cs index 2ccbe53c276a..f0756d532cc4 100644 --- a/src/Controls/src/SourceGen/SourceGenContext.cs +++ b/src/Controls/src/SourceGen/SourceGenContext.cs @@ -32,6 +32,7 @@ class SourceGenContext(IndentedTextWriter writer, Compilation compilation, Sourc public IList LocalMethods { get; } = new List(); public bool EnableLineInfo { get; set; } public bool EnableDiagnostics { get; internal set; } + public string TargetFramework { get; internal set; } = ""; public void AddLocalMethod(string code) { diff --git a/src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs b/src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs index 8a9a46c6a0f4..055878225759 100644 --- a/src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs +++ b/src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs @@ -3,139 +3,139 @@ #nullable disable -namespace Microsoft.Maui.Controls.Xaml -{ - class SimplifyOnPlatformVisitor : IXamlNodeVisitor +namespace Microsoft.Maui.Controls.Xaml; + +class SimplifyOnPlatformVisitor : IXamlNodeVisitor +{ public SimplifyOnPlatformVisitor(string targetFramework) { - public SimplifyOnPlatformVisitor(string targetFramework) - { - if (string.IsNullOrEmpty(targetFramework)) - return; - - if (targetFramework.IndexOf("-android", StringComparison.OrdinalIgnoreCase) != -1) - Target = nameof(OnPlatformExtension.Android); - if (targetFramework.IndexOf("-ios", StringComparison.OrdinalIgnoreCase) != -1) - Target = nameof(OnPlatformExtension.iOS); - if (targetFramework.IndexOf("-macos", StringComparison.OrdinalIgnoreCase) != -1) - Target = nameof(OnPlatformExtension.macOS); - if (targetFramework.IndexOf("-maccatalyst", StringComparison.OrdinalIgnoreCase) != -1) - Target = nameof(OnPlatformExtension.MacCatalyst); - } + if (string.IsNullOrEmpty(targetFramework)) + return; + + if (targetFramework.IndexOf("-android", StringComparison.OrdinalIgnoreCase) != -1) + Target = "Android"; + if (targetFramework.IndexOf("-ios", StringComparison.OrdinalIgnoreCase) != -1) + Target = "iOS"; + if (targetFramework.IndexOf("-macos", StringComparison.OrdinalIgnoreCase) != -1) + Target = "macOS"; + if (targetFramework.IndexOf("-maccatalyst", StringComparison.OrdinalIgnoreCase) != -1) + Target = "MacCatalyst"; + } - public string Target { get; } + public string Target { get; } - public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp; - public bool StopOnDataTemplate => false; - public bool VisitNodeOnDataTemplate => true; - public bool StopOnResourceDictionary => false; - public bool IsResourceDictionary(ElementNode node) => false; - public bool SkipChildren(INode node, INode parentNode) => false; + public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp; + public bool StopOnDataTemplate => false; + public bool VisitNodeOnDataTemplate => true; + public bool StopOnResourceDictionary => false; + public bool IsResourceDictionary(ElementNode node) => false; + public bool SkipChildren(INode node, INode parentNode) => false; - public void Visit(ValueNode node, INode parentNode) - { - } + public void Visit(ValueNode node, INode parentNode) + { + } - public void Visit(MarkupNode node, INode parentNode) - { - //markup was already expanded to element - } + public void Visit(MarkupNode node, INode parentNode) + { + //markup was already expanded to element + } - public void Visit(ElementNode node, INode parentNode) - { - if (Target is null) - return; + public void Visit(ElementNode node, INode parentNode) + { + if (Target is null) + return; - //`{OnPlatform}` markup extension - if (node.XmlType.Name == nameof(OnPlatformExtension) && node.XmlType.NamespaceUri == XamlParser.MauiUri) + //`{OnPlatform}` markup extension + if (node.XmlType.IsOfAnyType("OnPlatformExtension")) + { + if ( node.Properties.TryGetValue(new XmlName("", Target), out INode targetNode) + || node.Properties.TryGetValue(new XmlName(null, Target), out targetNode) + || node.Properties.TryGetValue(new XmlName("", "Default"), out targetNode) + || node.Properties.TryGetValue(new XmlName(null, "Default"), out targetNode)) { - if (node.Properties.TryGetValue(new XmlName("", Target), out INode targetNode) - || node.Properties.TryGetValue(new XmlName("", nameof(OnPlatformExtension.Default)), out targetNode)) - { - if (!node.TryGetPropertyName(parentNode, out XmlName name)) - return; - if (parentNode is IElementNode parentEnode) - parentEnode.Properties[name] = targetNode; - } - else if (node.CollectionItems.Count > 0) // syntax like {OnPlatform foo, iOS=bar} - { - if (!node.TryGetPropertyName(parentNode, out XmlName name)) - return; - if (parentNode is IElementNode parentEnode) - parentEnode.Properties[name] = node.CollectionItems[0]; - } - else //no prop for target and no Default set - { - if (!node.TryGetPropertyName(parentNode, out XmlName name)) - return; - //if there's no value for the targetPlatform, ignore the node. - //this is slightly different than what OnPlatform does (return default(T)) - if (parentNode is IElementNode parentEnode) - parentEnode.Properties.Remove(name); - } + if (!node.TryGetPropertyName(parentNode, out XmlName name)) + return; + if (parentNode is IElementNode parentEnode) + parentEnode.Properties[name] = targetNode; } + else if (node.CollectionItems.Count > 0) // syntax like {OnPlatform foo, iOS=bar} + { + if (!node.TryGetPropertyName(parentNode, out XmlName name)) + return; + if (parentNode is IElementNode parentEnode) + parentEnode.Properties[name] = node.CollectionItems[0]; + } + else //no prop for target and no Default set + { + if (!node.TryGetPropertyName(parentNode, out XmlName name)) + return; + //if there's no value for the targetPlatform, ignore the node. + //this is slightly different than what OnPlatform does (return default(T)) + if (parentNode is IElementNode parentEnode) + parentEnode.Properties.Remove(name); + } + } - //`` elements - //if (node.XmlType.Name == "OnPlatform" && node.XmlType.NamespaceUri == XamlParser.MauiUri) - //{ - // var onNode = GetOnNode(node, Target) ?? GetDefault(node); - - // //Property node - // if (node.TryGetPropertyName(parentNode, out XmlName name) - // && parentNode is IElementNode parentEnode) - // { - // if (onNode != null) - // parentEnode.Properties[name] = onNode; - // else - // parentEnode.Properties.Remove(name); - // return; - // } - - // //Collection item - // if (onNode != null && parentNode is IElementNode parentEnode2) - // parentEnode2.CollectionItems[parentEnode2.CollectionItems.IndexOf(node)] = onNode; - - //} - - //INode GetOnNode(ElementNode onPlatform, string target) - //{ - // foreach (var onNode in onPlatform.CollectionItems) - // { - // if ((onNode as ElementNode).Properties.TryGetValue(new XmlName("", "Platform"), out var platform)) - // { - // var splits = ((platform as ValueNode).Value as string).Split(','); - // foreach (var split in splits) - // { - // if (string.IsNullOrWhiteSpace(split)) - // continue; - // if (split.Trim() == target) - // { - // if ((onNode as ElementNode).Properties.TryGetValue(new XmlName("", "Value"), out var node)) - // return node; - - // return (onNode as ElementNode).CollectionItems.FirstOrDefault(); - // } - // } - // } - // } - // return null; - //} - - //INode GetDefault(ElementNode onPlatform) - //{ - // if (node.Properties.TryGetValue(new XmlName("", "Default"), out INode defaultNode)) - // return defaultNode; - // return null; - //} + //`` elements + //if (node.XmlType.Name == "OnPlatform" && node.XmlType.NamespaceUri == XamlParser.MauiUri) + //{ + // var onNode = GetOnNode(node, Target) ?? GetDefault(node); + + // //Property node + // if (node.TryGetPropertyName(parentNode, out XmlName name) + // && parentNode is IElementNode parentEnode) + // { + // if (onNode != null) + // parentEnode.Properties[name] = onNode; + // else + // parentEnode.Properties.Remove(name); + // return; + // } + + // //Collection item + // if (onNode != null && parentNode is IElementNode parentEnode2) + // parentEnode2.CollectionItems[parentEnode2.CollectionItems.IndexOf(node)] = onNode; + + //} + + //INode GetOnNode(ElementNode onPlatform, string target) + //{ + // foreach (var onNode in onPlatform.CollectionItems) + // { + // if ((onNode as ElementNode).Properties.TryGetValue(new XmlName("", "Platform"), out var platform)) + // { + // var splits = ((platform as ValueNode).Value as string).Split(','); + // foreach (var split in splits) + // { + // if (string.IsNullOrWhiteSpace(split)) + // continue; + // if (split.Trim() == target) + // { + // if ((onNode as ElementNode).Properties.TryGetValue(new XmlName("", "Value"), out var node)) + // return node; + + // return (onNode as ElementNode).CollectionItems.FirstOrDefault(); + // } + // } + // } + // } + // return null; + //} + + //INode GetDefault(ElementNode onPlatform) + //{ + // if (node.Properties.TryGetValue(new XmlName("", "Default"), out INode defaultNode)) + // return defaultNode; + // return null; + //} - } + } - public void Visit(RootNode node, INode parentNode) - { - } + public void Visit(RootNode node, INode parentNode) + { + } - public void Visit(ListNode node, INode parentNode) - { - } + public void Visit(ListNode node, INode parentNode) + { } } diff --git a/src/Controls/src/Xaml/XamlNode.cs b/src/Controls/src/Xaml/XamlNode.cs index aa20a2674bfb..79985054653d 100644 --- a/src/Controls/src/Xaml/XamlNode.cs +++ b/src/Controls/src/Xaml/XamlNode.cs @@ -41,50 +41,6 @@ class NameScopeRef public INameScope NameScope { get; set; } } - [DebuggerDisplay("{NamespaceUri}:{Name}")] - class XmlType - { - public XmlType(string namespaceUri, string name, IList typeArguments) - { - NamespaceUri = namespaceUri; - Name = name; - TypeArguments = typeArguments; - } - - public string NamespaceUri { get; } - public string Name { get; } - public IList 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 != 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 { protected BaseNode(IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1) diff --git a/src/Controls/src/Xaml/XmlType.cs b/src/Controls/src/Xaml/XmlType.cs new file mode 100644 index 000000000000..da31542b754e --- /dev/null +++ b/src/Controls/src/Xaml/XmlType.cs @@ -0,0 +1,50 @@ +#nullable disable +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.Maui.Controls.Xaml; + +[DebuggerDisplay("{NamespaceUri}:{Name}")] +class XmlType(string namespaceUri, string name, IList typeArguments) +{ + public string NamespaceUri { get; } = namespaceUri; + public string Name { get; } = name; + public IList TypeArguments { get; } = typeArguments; + + 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 != null && other.TypeArguments != null && TypeArguments.SequenceEqual(other.TypeArguments)); + } + + public bool IsOfAnyType(params string[] types) + { + if (types == null || types.Length == 0) + return false; + if (NamespaceUri != XamlParser.MauiUri && NamespaceUri != XamlParser.MauiGlobalUri) + return false; + if (types.Contains(Name)) + return true; + return false; + } + public override int GetHashCode() + { + unchecked + { +#if NETSTANDARD2_0 + int hashCode = NamespaceUri.GetHashCode(); + hashCode = (hashCode * 397) ^ Name.GetHashCode(); + return hashCode; +#else + return HashCode.Combine(NamespaceUri, Name); +#endif + } + } +} diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs new file mode 100644 index 000000000000..ae15c5941ab7 --- /dev/null +++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.SourceGen.UnitTests.InitializeComponent; + +public class SimplifyOnPlatform : SourceGenXamlInitializeComponentTestBase +{ + [Test] + public void Test() + { + var xaml = +""" + + + + + + +"""; + + var code = +""" +using System; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; + +namespace Test; + +[XamlProcessing(XamlInflator.SourceGen)] +public partial class TestPage : ContentPage +{ + public TestPage() + { + InitializeComponent(); + } +} +"""; + +var expected = +""" + +//------------------------------------------------------------------------------ +// +// This code was generated by a .NET MAUI source generator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Test; + +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null", "10.0.0.0")] +public partial class TestPage +{ + private partial void InitializeComponent() + { + var setter = new global::Microsoft.Maui.Controls.Setter(); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 14); + var setter1 = new global::Microsoft.Maui.Controls.Setter(); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 9, 14); + var xamlServiceProvider = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this); + var xmlNamespaceResolver = new global::Microsoft.Maui.Controls.Xaml.Internals.XmlNamespaceResolver(); + xmlNamespaceResolver.Add("__f__", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver.Add("__g__", "http://schemas.microsoft.com/dotnet/maui/global"); + xmlNamespaceResolver.Add("", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver.Add("x", "http://schemas.microsoft.com/winfx/2009/xaml"); + xamlServiceProvider.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver, typeof(global::Test.TestPage).Assembly)); + var style1 = new global::Microsoft.Maui.Controls.Style(((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.TypeTypeConverter()).ConvertFromInvariantString("Label", xamlServiceProvider) as global::System.Type); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(style1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 7, 10); + var __root = this; + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(__root!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 2, 2); +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.INameScope iNameScope = global::Microsoft.Maui.Controls.Internals.NameScope.GetNameScope(__root) ?? new global::Microsoft.Maui.Controls.Internals.NameScope(); +#endif +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope); +#endif +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.INameScope iNameScope1 = new global::Microsoft.Maui.Controls.Internals.NameScope(); +#endif +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.INameScope iNameScope2 = new global::Microsoft.Maui.Controls.Internals.NameScope(); +#endif +#line 8 "Test.xaml" + var xamlServiceProvider1 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this); + var iProvideValueTarget = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider( + new object[] {setter, style1, __root}, + typeof(global::Microsoft.Maui.Controls.Setter).GetProperty("Property"), +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + new [] { iNameScope1 }, +#else + null, +#endif + false); + xamlServiceProvider1.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IReferenceProvider), iProvideValueTarget); + xamlServiceProvider1.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IProvideValueTarget), iProvideValueTarget); + var xmlNamespaceResolver1 = new global::Microsoft.Maui.Controls.Xaml.Internals.XmlNamespaceResolver(); + xmlNamespaceResolver1.Add("__f__", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver1.Add("__g__", "http://schemas.microsoft.com/dotnet/maui/global"); + xmlNamespaceResolver1.Add("", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver1.Add("x", "http://schemas.microsoft.com/winfx/2009/xaml"); + xamlServiceProvider1.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver1, typeof(global::Test.TestPage).Assembly)); + setter.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("TextColor", xamlServiceProvider1) as global::Microsoft.Maui.Controls.BindableProperty; +#line default +#line 8 "Test.xaml" + setter.Value = "Pink"; +#line default + var setter2 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Color.Parse("Pink")}; + if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter2!) == null) + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter2!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 14); +#line 8 "Test.xaml" + ((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter2); +#line default +#line 9 "Test.xaml" + var xamlServiceProvider2 = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(this); + var iProvideValueTarget1 = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider( + new object[] {setter1, style1, __root}, + typeof(global::Microsoft.Maui.Controls.Setter).GetProperty("Property"), +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + new [] { iNameScope2 }, +#else + null, +#endif + false); + xamlServiceProvider2.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IReferenceProvider), iProvideValueTarget1); + xamlServiceProvider2.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IProvideValueTarget), iProvideValueTarget1); + var xmlNamespaceResolver2 = new global::Microsoft.Maui.Controls.Xaml.Internals.XmlNamespaceResolver(); + xmlNamespaceResolver2.Add("__f__", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver2.Add("__g__", "http://schemas.microsoft.com/dotnet/maui/global"); + xmlNamespaceResolver2.Add("", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver2.Add("x", "http://schemas.microsoft.com/winfx/2009/xaml"); + xamlServiceProvider2.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver2, typeof(global::Test.TestPage).Assembly)); + setter1.Property = ((global::Microsoft.Maui.Controls.IExtendedTypeConverter)new global::Microsoft.Maui.Controls.BindablePropertyConverter()).ConvertFromInvariantString("IsVisible", xamlServiceProvider2) as global::Microsoft.Maui.Controls.BindableProperty; +#line default +#line 1 "Test.xaml" + setter1.Value = "True"; +#line default + var setter3 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.VisualElement.IsVisibleProperty, Value = (bool)new global::Microsoft.Maui.Controls.VisualElement.VisibilityConverter().ConvertFromInvariantString("True")!}; + if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter3!) == null) + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter3!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 9, 14); +#line 9 "Test.xaml" + ((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter3); +#line default + var resourceDictionary = __root.Resources; + resourceDictionary.Add("style", style1); + } +} + +"""; + + var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-android"); + Assert.IsFalse(result.Diagnostics.Any()); + + Assert.AreEqual(expected, generated); + + } +} \ No newline at end of file diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs index 24ef63dff778..c546788146d5 100644 --- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SourceGenXamlInitializeComponentTests.cs @@ -9,16 +9,16 @@ namespace Microsoft.Maui.Controls.SourceGen.UnitTests; public class SourceGenXamlInitializeComponentTestBase : SourceGenTestsBase { - protected record AdditionalXamlFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null) - : AdditionalFile(Text: ToAdditionalText(Path, Content), Kind: "Xaml", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName, TargetFramework: TargetFramework); + protected record AdditionalXamlFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null, string? NoWarn = null) + : AdditionalFile(Text: ToAdditionalText(Path, Content), Kind: "Xaml", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName, TargetFramework: TargetFramework, NoWarn: NoWarn); - protected (GeneratorDriverRunResult result, string? text) RunGenerator(string xaml, string code, string noWarn = "") + protected (GeneratorDriverRunResult result, string? text) RunGenerator(string xaml, string code, string noWarn = "", string targetFramework = "") { var compilation = CreateMauiCompilation(); compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code)); - var result = RunGenerator(compilation, noWarn, new AdditionalXamlFile("Test.xaml", xaml)); + var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml, TargetFramework: targetFramework, NoWarn: noWarn)); var generated = result.Results.SingleOrDefault().GeneratedSources.SingleOrDefault(gs => gs.HintName.EndsWith(".xsg.cs")).SourceText?.ToString(); return (result, generated); } -} \ No newline at end of file +} diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs index 799a1b4cb000..2cffaae46b57 100644 --- a/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests.SourceGen; public class SourceGenCssTests : SourceGenTestsBase { private record AdditionalCssFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null) - : AdditionalFile(Text: SourceGeneratorDriver.ToAdditionalText(Path, Content), Kind: "Css", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName ?? Path, TargetFramework: TargetFramework); + : AdditionalFile(Text: SourceGeneratorDriver.ToAdditionalText(Path, Content), Kind: "Css", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName ?? Path, TargetFramework: TargetFramework, NoWarn: ""); [Test] public void TestCodeBehindGenerator_BasicCss() diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlCodeBehindTests.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlCodeBehindTests.cs index ec94ad26b611..e54a64be956c 100644 --- a/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlCodeBehindTests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlCodeBehindTests.cs @@ -12,8 +12,8 @@ namespace Microsoft.Maui.Controls.Xaml.UnitTests.SourceGen; public class SourceGenXamlCodeBehindTests : SourceGenTestsBase { - private record AdditionalXamlFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null) - : AdditionalFile(Text: SourceGeneratorDriver.ToAdditionalText(Path, Content), Kind: "Xaml", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName, TargetFramework: TargetFramework); + private record AdditionalXamlFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null, string? NoWarn = null) + : AdditionalFile(Text: SourceGeneratorDriver.ToAdditionalText(Path, Content), Kind: "Xaml", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName, TargetFramework: TargetFramework, NoWarn: NoWarn); [Test] public void TestCodeBehindGenerator_BasicXaml() diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs index 132356258d84..cabf7a696c9d 100644 --- a/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs +++ b/src/Controls/tests/SourceGen.UnitTests/SourceGeneratorDriver.cs @@ -18,11 +18,9 @@ public static class SourceGeneratorDriver { private static MetadataReference[]? MauiReferences; - public record AdditionalFile(AdditionalText Text, string Kind, string RelativePath, string? TargetPath, string? ManifestResourceName, string? TargetFramework); + public record AdditionalFile(AdditionalText Text, string Kind, string RelativePath, string? TargetPath, string? ManifestResourceName, string? TargetFramework, string? NoWarn); public static GeneratorDriverRunResult RunGenerator(Compilation compilation, params AdditionalFile[] additionalFiles) - where T : IIncrementalGenerator, new() => RunGenerator(compilation, "", additionalFiles); - public static GeneratorDriverRunResult RunGenerator(Compilation compilation, string noWarn, params AdditionalFile[] additionalFiles) where T : IIncrementalGenerator, new() { ISourceGenerator generator = new T().AsSourceGenerator(); @@ -33,8 +31,8 @@ public static GeneratorDriverRunResult RunGenerator(Compilation compilation, trackIncrementalGeneratorSteps: true); GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], driverOptions: options) - .AddAdditionalTexts(additionalFiles.Select(f => f.Text).ToImmutableArray()) - .WithUpdatedAnalyzerConfigOptions(new CustomAnalyzerConfigOptionsProvider(additionalFiles, noWarn)); + .AddAdditionalTexts([.. additionalFiles.Select(f => f.Text)]) + .WithUpdatedAnalyzerConfigOptions(new CustomAnalyzerConfigOptionsProvider(additionalFiles)); driver = driver.RunGenerators(compilation); @@ -108,12 +106,10 @@ private static MetadataReference[] GetMauiReferences() private class CustomAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider { private readonly IImmutableDictionary _additionalFiles; - private readonly string _noWarn; - public CustomAnalyzerConfigOptionsProvider(AdditionalFile[] additionalFiles, string noWarn = "") + public CustomAnalyzerConfigOptionsProvider(AdditionalFile[] additionalFiles) { _additionalFiles = additionalFiles.ToImmutableDictionary(f => f.Text.Path, f => f); - _noWarn = noWarn; } public override AnalyzerConfigOptions GlobalOptions => throw new System.NotImplementedException(); @@ -126,22 +122,20 @@ public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) { return _additionalFiles.TryGetValue(textFile.Path, out var additionalFile) - ? (AnalyzerConfigOptions)new CustomAnalyzerConfigOptions(additionalFile, _noWarn) + ? (AnalyzerConfigOptions)new CustomAnalyzerConfigOptions(additionalFile) : CustomAnalyzerConfigOptions.Empty; } private class CustomAnalyzerConfigOptions : AnalyzerConfigOptions { readonly AdditionalFile? _additionalFile; - readonly string _noWarn; - public CustomAnalyzerConfigOptions(AdditionalFile? additionalFile, string noWarn) + public CustomAnalyzerConfigOptions(AdditionalFile? additionalFile) { _additionalFile = additionalFile; - _noWarn = noWarn; } - public static AnalyzerConfigOptions Empty { get; } = new CustomAnalyzerConfigOptions(null, ""); + public static AnalyzerConfigOptions Empty { get; } = new CustomAnalyzerConfigOptions(null); public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { @@ -161,7 +155,7 @@ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? val "build_property.targetframework" => _additionalFile.TargetFramework, "build_property.EnableMauiXamlDiagnostics" => "true", "build_property.MauiXamlLineInfo" => "enable", - "build_property.MauiXamlNoWarn" => _noWarn, + "build_property.MauiXamlNoWarn" => _additionalFile.NoWarn, _ => null }; diff --git a/src/Controls/tests/Xaml.UnitTests/MockSourceGenerator.cs b/src/Controls/tests/Xaml.UnitTests/MockSourceGenerator.cs index c267f9b4574c..200f9bf2928c 100644 --- a/src/Controls/tests/Xaml.UnitTests/MockSourceGenerator.cs +++ b/src/Controls/tests/Xaml.UnitTests/MockSourceGenerator.cs @@ -41,13 +41,13 @@ public record AdditionalXamlFile(string Path, string Content, string? RelativePa public static GeneratorDriverRunResult RunMauiSourceGenerator(Type xamlType) => CreateMauiCompilation().RunMauiSourceGenerator(xamlType); - public static GeneratorDriverRunResult RunMauiSourceGenerator(this Compilation compilation, Type xamlType) + public static GeneratorDriverRunResult RunMauiSourceGenerator(this Compilation compilation, Type xamlType, string targetFramework = "") { var resourceId = XamlResourceIdAttribute.GetResourceIdForType(xamlType); var resourcePath = XamlResourceIdAttribute.GetPathForType(xamlType); var resourceStream = typeof(MockSourceGenerator).Assembly.GetManifestResourceStream(resourceId); - return RunMauiSourceGenerator(compilation, new AdditionalXamlFile(resourcePath, new StreamReader(resourceStream!).ReadToEnd())); + return RunMauiSourceGenerator(compilation, new AdditionalXamlFile(resourcePath, new StreamReader(resourceStream!).ReadToEnd(), TargetFramework: targetFramework)); } static string GetTopDirRecursive(string searchDirectory, int maxSearchDepth = 7) diff --git a/src/Controls/tests/Xaml.UnitTests/OnPlatformOptimization.xaml.cs b/src/Controls/tests/Xaml.UnitTests/OnPlatformOptimization.xaml.cs index 8562403e2a25..ba411072cfaf 100644 --- a/src/Controls/tests/Xaml.UnitTests/OnPlatformOptimization.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/OnPlatformOptimization.xaml.cs @@ -3,6 +3,7 @@ using Mono.Cecil.Cil; using NUnit.Framework; +using static Microsoft.Maui.Controls.Xaml.UnitTests.MockSourceGenerator; namespace Microsoft.Maui.Controls.Xaml.UnitTests; public partial class OnPlatformOptimization : ContentPage @@ -13,14 +14,43 @@ public partial class OnPlatformOptimization : ContentPage class Tests { [Test] - public void OnPlatformExtensionsAreSimplified([Values("net7.0-ios", "net7.0-android")] string targetFramework) + public void OnPlatformExtensionsAreSimplified([Values("net7.0-ios", "net7.0-android")] string targetFramework, [Values(XamlInflator.XamlC, XamlInflator.SourceGen)] XamlInflator inflator) { - MockCompiler.Compile(typeof(OnPlatformOptimization), out var methodDef, out var hasLoggedErrors, targetFramework); - Assert.That(!hasLoggedErrors); - Assert.That(!methodDef.Body.Instructions.Any(instr => InstructionIsOnPlatformExtensionCtor(methodDef, instr)), "This Xaml still generates a new OnPlatformExtension()"); + if (inflator == XamlInflator.XamlC) + { + MockCompiler.Compile(typeof(OnPlatformOptimization), out var methodDef, out var hasLoggedErrors, targetFramework); + Assert.That(!hasLoggedErrors); + Assert.That(!methodDef.Body.Instructions.Any(instr => InstructionIsOnPlatformExtensionCtor(methodDef, instr)), "This Xaml still generates a new OnPlatformExtension()"); + + var expected = targetFramework.EndsWith("-ios") ? "bar" : "foo"; + Assert.That(methodDef.Body.Instructions.Any(instr => instr.Operand as string == expected), $"Did not find instruction containing '{expected}'"); + } + else if (inflator == XamlInflator.SourceGen) + { + var result = CreateMauiCompilation() + .WithAdditionalSource( +""" +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using NUnit.Framework; + +using static Microsoft.Maui.Controls.Xaml.UnitTests.MockSourceGenerator; +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class OnPlatformOptimization : ContentPage +{ + public OnPlatformOptimization() => InitializeComponent(); +} +""") + .RunMauiSourceGenerator(typeof(OnPlatformOptimization), targetFramework: targetFramework); + Assert.That(result.Diagnostics, Is.Empty); - var expected = targetFramework.EndsWith("-ios") ? "bar" : "foo"; - Assert.That(methodDef.Body.Instructions.Any(instr => instr.Operand as string == expected), $"Did not find instruction containing '{expected}'"); + var generated = result.GeneratedInitializeComponent(); + Assert.That(generated, Does.Not.Contain("OnPlatformExtension")); + var expected = targetFramework.EndsWith("-ios") ? "bar" : "foo"; + Assert.That(generated, Does.Contain($"SetValue(global::Microsoft.Maui.Controls.Label.TextProperty, \"{expected}\");")); + } } [Test]