diff --git a/.gitignore b/.gitignore index 8f457824a432..ff6d3acd7fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ bld/ # Visual Studio 2017 auto generated files Generated\ Files/ +Generated/ # MSTest test Results [Tt]est[Rr]esult*/ diff --git a/Microsoft.Maui-vscode.sln b/Microsoft.Maui-vscode.sln index d74ad80a1185..d61f2e1e3fd5 100644 --- a/Microsoft.Maui-vscode.sln +++ b/Microsoft.Maui-vscode.sln @@ -212,6 +212,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITest.Analyzers", "src\Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maui.Controls.Sample.Embedding", "src\Controls\samples\Controls.Sample.Embedding\Maui.Controls.Sample.Embedding.csproj", "{4ADCBA87-30DB-44F5-85E9-94A4F4132FD9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGen.UnitTests", "src\Controls\tests\SourceGen.UnitTests\SourceGen.UnitTests.csproj", "{A426B2FC-F012-436B-BDD9-BEC0025DB96B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -539,6 +541,10 @@ Global {4ADCBA87-30DB-44F5-85E9-94A4F4132FD9}.Release|Any CPU.ActiveCfg = Release|Any CPU {4ADCBA87-30DB-44F5-85E9-94A4F4132FD9}.Release|Any CPU.Build.0 = Release|Any CPU {4ADCBA87-30DB-44F5-85E9-94A4F4132FD9}.Release|Any CPU.Deploy.0 = Release|Any CPU + {A426B2FC-F012-436B-BDD9-BEC0025DB96B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A426B2FC-F012-436B-BDD9-BEC0025DB96B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A426B2FC-F012-436B-BDD9-BEC0025DB96B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A426B2FC-F012-436B-BDD9-BEC0025DB96B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -636,6 +642,7 @@ Global {0048EA9A-D751-4576-A2BB-2A37BFB385A5} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E} {DA001142-4777-4EDE-97D5-B1AC08162F99} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C} {4ADCBA87-30DB-44F5-85E9-94A4F4132FD9} = {E1082E26-D700-4127-9329-66D673FD2D55} + {A426B2FC-F012-436B-BDD9-BEC0025DB96B} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50} diff --git a/src/Controls/docs/Microsoft.Maui.Controls/XmlnsDefinitionAttribute.xml b/src/Controls/docs/Microsoft.Maui.Controls/XmlnsDefinitionAttribute.xml index fa5fd8ab4c07..2ccabffcd8f8 100644 --- a/src/Controls/docs/Microsoft.Maui.Controls/XmlnsDefinitionAttribute.xml +++ b/src/Controls/docs/Microsoft.Maui.Controls/XmlnsDefinitionAttribute.xml @@ -24,8 +24,8 @@ - - + + Constructor @@ -35,11 +35,11 @@ - + To be added. - To be added. + To be added. @@ -74,6 +74,22 @@ + + + + + + Property + + Microsoft.Maui.Controls.Core + 2.0.0.0 + + + System.String + + + + diff --git a/src/Controls/src/Build.Tasks/ModuleDefinitionExtensions.cs b/src/Controls/src/Build.Tasks/ModuleDefinitionExtensions.cs index 41fe0c965666..34c33621cbdd 100644 --- a/src/Controls/src/Build.Tasks/ModuleDefinitionExtensions.cs +++ b/src/Controls/src/Build.Tasks/ModuleDefinitionExtensions.cs @@ -254,7 +254,8 @@ public static TypeDefinition GetTypeDefinition(this ModuleDefinition module, Xam { return cache.GetOrAddTypeDefinition(module, type, x => { - var asm = module.Assembly.Name.Name == type.assemblyName + var assemblyName = module.Assembly.Name; + var asm = (assemblyName.Name == type.assemblyName || assemblyName.FullName == type.assemblyName) ? module.Assembly : module.AssemblyResolver.Resolve(AssemblyNameReference.Parse(type.assemblyName)); var typeDef = asm.MainModule.GetType($"{type.clrNamespace}.{type.typeName}"); diff --git a/src/Controls/src/Build.Tasks/TypeReferenceExtensions.cs b/src/Controls/src/Build.Tasks/TypeReferenceExtensions.cs index 5c20c1dcd64d..9e7a251d503c 100644 --- a/src/Controls/src/Build.Tasks/TypeReferenceExtensions.cs +++ b/src/Controls/src/Build.Tasks/TypeReferenceExtensions.cs @@ -9,23 +9,14 @@ namespace Microsoft.Maui.Controls.Build.Tasks { class TypeRefComparer : IEqualityComparer { - static string GetAssemblyName(TypeReference typeRef) - { - if (typeRef.Scope is ModuleDefinition md) - return md.Assembly.Name.Name; - if (typeRef.Scope is AssemblyNameReference anr) - return anr.Name; - throw new ArgumentOutOfRangeException(nameof(typeRef)); - } - public bool Equals(TypeReference x, TypeReference y) { - if (x == null) - return y == null; - if (y == null) - return x == null; + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; - //strip the leading `&` as byref typered fullnames have a `&` + //strip the leading `&` as byref typeref fullnames have a `&` var xname = x.FullName.EndsWith("&", StringComparison.InvariantCulture) ? x.FullName.Substring(0, x.FullName.Length - 1) : x.FullName; var yname = y.FullName.EndsWith("&", StringComparison.InvariantCulture) ? y.FullName.Substring(0, y.FullName.Length - 1) : y.FullName; if (xname != yname) @@ -34,27 +25,40 @@ public bool Equals(TypeReference x, TypeReference y) var yasm = GetAssemblyName(y); //standard types comes from either mscorlib. System.Runtime or netstandard. Assume they are equivalent - if ((xasm.StartsWith("System.Runtime", StringComparison.Ordinal) - || xasm.StartsWith("System", StringComparison.Ordinal) - || xasm.StartsWith("mscorlib", StringComparison.Ordinal) - || xasm.StartsWith("netstandard", StringComparison.Ordinal) - || xasm.StartsWith("System.Xml", StringComparison.Ordinal)) - && (yasm.StartsWith("System.Runtime", StringComparison.Ordinal) - || yasm.StartsWith("System", StringComparison.Ordinal) - || yasm.StartsWith("mscorlib", StringComparison.Ordinal) - || yasm.StartsWith("netstandard", StringComparison.Ordinal) - || yasm.StartsWith("System.Xml", StringComparison.Ordinal))) + if (IsSystemAssemvly(xasm) && IsSystemAssemvly(yasm)) return true; return xasm == yasm; } public int GetHashCode(TypeReference obj) { - return $"{GetAssemblyName(obj)}//{obj.FullName}".GetHashCode(); + var assemblyName = GetAssemblyName(obj); + if (IsSystemAssemvly(assemblyName)) + return obj.FullName.GetHashCode(); + return assemblyName.GetHashCode() ^ obj.FullName.GetHashCode(); + } + + static string GetAssemblyName(TypeReference typeRef) + { + if (typeRef.Scope is ModuleDefinition md) + return md.Assembly.Name.Name; + if (typeRef.Scope is AssemblyNameReference anr) + return anr.Name; + throw new ArgumentOutOfRangeException(nameof(typeRef)); + } + + bool IsSystemAssemvly(string assemblyName) + { + return assemblyName.StartsWith("System", StringComparison.Ordinal) + || assemblyName.StartsWith("mscorlib", StringComparison.Ordinal) + || assemblyName.StartsWith("netstandard", StringComparison.Ordinal) + || assemblyName.StartsWith("System.Runtime", StringComparison.Ordinal) + || assemblyName.StartsWith("System.Xml", StringComparison.Ordinal) + || assemblyName.StartsWith("System.Private.CoreLib", StringComparison.Ordinal); } static TypeRefComparer s_default; - public static TypeRefComparer Default => s_default ?? (s_default = new TypeRefComparer()); + public static TypeRefComparer Default => s_default ??= new TypeRefComparer(); } static class TypeReferenceExtensions diff --git a/src/Controls/src/Build.Tasks/XamlCTask.cs b/src/Controls/src/Build.Tasks/XamlCTask.cs index 9592b9cc21e5..f910a08643fa 100644 --- a/src/Controls/src/Build.Tasks/XamlCTask.cs +++ b/src/Controls/src/Build.Tasks/XamlCTask.cs @@ -285,7 +285,7 @@ public override bool Execute(out IList thrownExceptions) ILRootNode rootnode = null; try { - rootnode = ParseXaml(resource.GetResourceStream(), typeDef); + rootnode = ParseXaml(resource.GetResourceStream(), module, typeDef); if (rootnode == null) { LoggingHelper.LogMessage(Low, $"{new string(' ', 8)}failed."); diff --git a/src/Controls/src/Build.Tasks/XamlTask.cs b/src/Controls/src/Build.Tasks/XamlTask.cs index 1b0150150026..5d04f2f7a930 100644 --- a/src/Controls/src/Build.Tasks/XamlTask.cs +++ b/src/Controls/src/Build.Tasks/XamlTask.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Xml; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -40,9 +41,23 @@ public bool Execute() public abstract bool Execute(out IList thrownExceptions); - internal static ILRootNode ParseXaml(Stream stream, TypeReference typeReference) + internal static ILRootNode ParseXaml(Stream stream, ModuleDefinition module, TypeReference typeReference) { - using (var reader = XmlReader.Create(stream)) + var allowImplicitXmlns = module.Assembly.CustomAttributes.Any(a => + a.AttributeType.FullName == typeof(Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute).FullName + && (a.ConstructorArguments.Count == 0 || a.ConstructorArguments[0].Value is bool b && b)); + + var nsmgr = new XmlNamespaceManager(new NameTable()); + if (allowImplicitXmlns) + { + nsmgr.AddNamespace("", XamlParser.DefaultImplicitUri); + foreach (var xmlnsPrefix in XmlTypeExtensions.GetXmlnsPrefixAttributes(module)) + nsmgr.AddNamespace(xmlnsPrefix.Prefix, xmlnsPrefix.XmlNamespace); + } + + using (var reader = XmlReader.Create(stream, + new XmlReaderSettings { ConformanceLevel = allowImplicitXmlns ? ConformanceLevel.Fragment : ConformanceLevel.Document }, + new XmlParserContext(nsmgr.NameTable, nsmgr, null, XmlSpace.None))) { while (reader.Read()) { @@ -73,8 +88,22 @@ public static bool IsXaml(this EmbeddedResource resource, XamlCache cache, Modul if (!resource.Name.EndsWith(".xaml", StringComparison.InvariantCulture)) return false; + var allowImplicitXmlns = module.Assembly.CustomAttributes.Any(a => + a.AttributeType.FullName == typeof(Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute).FullName + && (a.ConstructorArguments.Count == 0 || a.ConstructorArguments[0].Value is bool b && b)); + + var nsmgr = new XmlNamespaceManager(new NameTable()); + nsmgr.AddNamespace("__f__", XamlParser.MauiUri); + if (allowImplicitXmlns) + { + nsmgr.AddNamespace("", XamlParser.DefaultImplicitUri); + foreach (var xmlnsPrefix in XmlTypeExtensions.GetXmlnsPrefixAttributes(module)) + nsmgr.AddNamespace(xmlnsPrefix.Prefix, xmlnsPrefix.XmlNamespace); + } using (var resourceStream = resource.GetResourceStream()) - using (var reader = XmlReader.Create(resourceStream)) + using (var reader = XmlReader.Create(resourceStream, + new XmlReaderSettings { ConformanceLevel = allowImplicitXmlns ? ConformanceLevel.Fragment : ConformanceLevel.Document }, + new XmlParserContext(nsmgr.NameTable, nsmgr, null, XmlSpace.None))) { // Read to the first Element while (reader.Read() && reader.NodeType != XmlNodeType.Element) diff --git a/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs b/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs index f11ac9eb3b88..1884ec126b54 100644 --- a/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs +++ b/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -10,6 +11,8 @@ namespace Microsoft.Maui.Controls.Build.Tasks { static class XmlTypeExtensions { + static readonly string _xmlnsDefinitionName = typeof(XmlnsDefinitionAttribute).FullName; + static IList GatherXmlnsDefinitionAttributes(ModuleDefinition module) { var xmlnsDefinitions = new List(); @@ -18,22 +21,17 @@ static IList GatherXmlnsDefinitionAttributes(ModuleDef { // Search for the attribute in the assemblies being // referenced. + GatherXmlnsDefinitionAttributes(xmlnsDefinitions, module.Assembly, module.Assembly); + foreach (var asmRef in module.AssemblyReferences) { var asmDef = module.AssemblyResolver.Resolve(asmRef); - foreach (var ca in asmDef.CustomAttributes) - { - if (ca.AttributeType.FullName == typeof(XmlnsDefinitionAttribute).FullName) - { - var attr = GetXmlnsDefinition(ca, asmDef); - xmlnsDefinitions.Add(attr); - } - } + GatherXmlnsDefinitionAttributes(xmlnsDefinitions, asmDef, module.Assembly); } } else { - // Use standard XF assemblies + // Use standard MAUI assemblies // (Should only happen in unit tests) var requiredAssemblies = new[] { typeof(XamlLoader).Assembly, @@ -42,13 +40,46 @@ static IList GatherXmlnsDefinitionAttributes(ModuleDef foreach (var assembly in requiredAssemblies) foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute), false)) { - attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName; + attribute.AssemblyName ??= assembly.FullName; + ValidateProtectedXmlns(attribute.XmlNamespace, attribute.AssemblyName); xmlnsDefinitions.Add(attribute); } } return xmlnsDefinitions; } + static void ValidateProtectedXmlns(string xmlNamespace, string assemblyName) + { + //maui, and x: xmlns are protected + if (xmlNamespace != XamlParser.MauiUri && xmlNamespace != XamlParser.X2009Uri) + return; + + //we know thos assemblies, they are fine in maui or x xmlns + if (assemblyName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) + || assemblyName.StartsWith("System", StringComparison.OrdinalIgnoreCase) + || assemblyName.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase)) + return; + + throw new BuildException(BuildExceptionCode.InvalidXaml, null, null, + $"Protected Xmlns {xmlNamespace}. Can't add assembly {assemblyName}."); + + } + static void GatherXmlnsDefinitionAttributes(List xmlnsDefinitions, AssemblyDefinition asmDef, AssemblyDefinition currentAssembly) + { + foreach (var ca in asmDef.CustomAttributes) + { + if (ca.AttributeType.FullName == _xmlnsDefinitionName) + { + var attr = GetXmlnsDefinition(ca, asmDef); + //only add globalxmlns definition from the current assembly + if (attr.XmlNamespace == XamlParser.MauiGlobalUri + && asmDef != currentAssembly) + continue; + ValidateProtectedXmlns(attr.XmlNamespace, attr.AssemblyName); + xmlnsDefinitions.Add(attr); + } + } + } public static TypeReference GetTypeReference(XamlCache cache, string typeName, ModuleDefinition module, BaseNode node, bool expandToExtension = true) { @@ -74,8 +105,10 @@ public static bool TryGetTypeReference(this XmlType xmlType, XamlCache cache, Mo var typeArguments = xmlType.TypeArguments; - TypeReference type = xmlType.GetTypeReference(xmlnsDefinitions, module.Assembly.Name.Name, (typeInfo) => + IEnumerable types = xmlType.GetTypeReferences(xmlnsDefinitions, module.Assembly.Name.Name, (typeInfo) => { + if (typeInfo.clrNamespace.StartsWith("http")) //aggregated xmlns, might result in a typeload exception + return null; string typeName = typeInfo.typeName.Replace('+', '/'); //Nested types var type = module.GetTypeDefinition(cache, (typeInfo.assemblyName, typeInfo.clrNamespace, typeName)); if (type is not null && type.IsPublicOrVisibleInternal(module)) @@ -83,6 +116,13 @@ public static bool TryGetTypeReference(this XmlType xmlType, XamlCache cache, Mo return null; }, expandToExtension: expandToExtension); + if (types.Distinct(TypeRefComparer.Default).Skip(1).Any()) + { + typeReference = null; + return false; + } + + var type = types.Distinct(TypeRefComparer.Default).FirstOrDefault(); if (type != null && typeArguments != null && type.HasGenericParameters) type = module.ImportReference(type).MakeGenericInstanceType(typeArguments.Select(x => x.GetTypeReference(cache, module, xmlInfo)).ToArray()); @@ -97,7 +137,7 @@ public static TypeReference GetTypeReference(this XmlType xmlType, XamlCache cac throw new BuildException(BuildExceptionCode.TypeResolution, xmlInfo, null, $"{xmlType.NamespaceUri}:{xmlType.Name}"); } - public static XmlnsDefinitionAttribute GetXmlnsDefinition(this CustomAttribute ca, AssemblyDefinition asmDef) + static XmlnsDefinitionAttribute GetXmlnsDefinition(this CustomAttribute ca, AssemblyDefinition asmDef) { var attr = new XmlnsDefinitionAttribute( ca.ConstructorArguments[0].Value as string, @@ -109,5 +149,48 @@ public static XmlnsDefinitionAttribute GetXmlnsDefinition(this CustomAttribute c attr.AssemblyName = assemblyName ?? asmDef.Name.FullName; return attr; } + + public static IList GetXmlnsPrefixAttributes(ModuleDefinition module) + { + var xmlnsPrefixes = new List(); + foreach (var ca in module.Assembly.CustomAttributes) + { + if (ca.AttributeType.FullName == typeof(XmlnsPrefixAttribute).FullName) + { + var attr = new XmlnsPrefixAttribute( + ca.ConstructorArguments[0].Value as string, + ca.ConstructorArguments[1].Value as string); + xmlnsPrefixes.Add(attr); + } + } + + if (module.AssemblyReferences?.Count > 0) + { + // Search for the attribute in the assemblies being + // referenced. + foreach (var asmRef in module.AssemblyReferences) + { + try + { + var asmDef = module.AssemblyResolver.Resolve(asmRef); + foreach (var ca in asmDef.CustomAttributes) + { + if (ca.AttributeType.FullName == typeof(XmlnsPrefixAttribute).FullName) + { + var attr = new XmlnsPrefixAttribute( + ca.ConstructorArguments[0].Value as string, + ca.ConstructorArguments[1].Value as string); + xmlnsPrefixes.Add(attr); + } + } + } + catch (System.Exception) + { + // Ignore assembly resolution errors + } + } + } + return xmlnsPrefixes; + } } } diff --git a/src/Controls/src/Core/AllowImplicitXmlnsDeclarationAttribute.cs b/src/Controls/src/Core/AllowImplicitXmlnsDeclarationAttribute.cs new file mode 100644 index 000000000000..a57728973470 --- /dev/null +++ b/src/Controls/src/Core/AllowImplicitXmlnsDeclarationAttribute.cs @@ -0,0 +1,14 @@ +#nullable enable +using System; +using System.Runtime.Versioning; + +namespace Microsoft.Maui.Controls.Xaml.Internals; + +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] +#if !NETSTANDARD +[RequiresPreviewFeatures] +#endif +public sealed class AllowImplicitXmlnsDeclarationAttribute(bool allow = true) : Attribute +{ + public bool Allow { get; } = allow; +} \ No newline at end of file diff --git a/src/Controls/src/Core/Properties/AssemblyInfo.cs b/src/Controls/src/Core/Properties/AssemblyInfo.cs index c95a4a14a643..19b2a8013ade 100644 --- a/src/Controls/src/Core/Properties/AssemblyInfo.cs +++ b/src/Controls/src/Core/Properties/AssemblyInfo.cs @@ -80,6 +80,7 @@ [assembly: XmlnsPrefix("http://schemas.microsoft.com/dotnet/2021/maui", "maui")] [assembly: XmlnsPrefix("http://schemas.microsoft.com/dotnet/2021/maui/design", "d")] +[assembly: XmlnsPrefix("http://schemas.microsoft.com/winfx/2009/xaml", "x")] [assembly: StyleProperty("background-color", typeof(VisualElement), nameof(VisualElement.BackgroundColorProperty))] [assembly: StyleProperty("background", typeof(VisualElement), nameof(VisualElement.BackgroundProperty))] diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt index ce68bc5c9d50..ae9a487a3c9f 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt @@ -1907,7 +1907,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index b0faa838339d..8dcbf3f0b33d 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -150,4 +150,8 @@ static readonly Microsoft.Maui.Controls.TitleBar.SubtitleProperty -> Microsoft.M static readonly Microsoft.Maui.Controls.TitleBar.TitleProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.TitleBar.TrailingContentProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty! -virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void \ No newline at end of file +virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt index 92ab1bbb6309..9b2241faac8c 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Shipped.txt @@ -1866,7 +1866,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 9089f402c400..164c58ed3dfd 100644 --- a/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -349,3 +349,7 @@ virtual Microsoft.Maui.Controls.Handlers.Items2.ItemsViewHandler2.Up *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.SetNeedsLayout() -> void *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.MovedToWindow() -> void override Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer.MovedToWindow() -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt index 92ab1bbb6309..9b2241faac8c 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt @@ -1866,7 +1866,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 44b640bc304f..d00e20c000d4 100644 --- a/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -348,4 +348,8 @@ virtual Microsoft.Maui.Controls.Handlers.Items2.ItemsViewDelegator2.UpdateLayout() -> void *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.SetNeedsLayout() -> void *REMOVED*override Microsoft.Maui.Controls.Handlers.Compatibility.FrameRenderer.MovedToWindow() -> void -override Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer.MovedToWindow() -> void \ No newline at end of file +override Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer.MovedToWindow() -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Shipped.txt index ba7dcea67a25..94d4cededcd8 100644 --- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Shipped.txt @@ -1685,7 +1685,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt index 4ee051439ef7..b0f7fa86070c 100644 --- a/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt @@ -143,4 +143,8 @@ static readonly Microsoft.Maui.Controls.TitleBar.SubtitleProperty -> Microsoft.M static readonly Microsoft.Maui.Controls.TitleBar.TitleProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.TitleBar.TrailingContentProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty! -virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void \ No newline at end of file +virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Shipped.txt index bc3a72d2f293..e5b38dca7a4f 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Shipped.txt @@ -1755,7 +1755,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index c2190ccab4e5..c4ccca43a025 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -149,4 +149,8 @@ static readonly Microsoft.Maui.Controls.TitleBar.SubtitleProperty -> Microsoft.M static readonly Microsoft.Maui.Controls.TitleBar.TitleProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.TitleBar.TrailingContentProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty! -virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void \ No newline at end of file +virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Shipped.txt index 3792b5f9c96a..e2c9056a24a6 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Shipped.txt @@ -1681,7 +1681,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index 0950e21053b6..076590508d60 100644 --- a/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt @@ -11,6 +11,9 @@ *REMOVED*~Microsoft.Maui.Controls.NavigableElement.StyleClass.set -> void Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget(T! target) -> void Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.Style? +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void ~Microsoft.Maui.Controls.Internals.TypedBindingBase.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.ResourceDictionary.SetAndCreateSource(System.Uri value) -> void ~Microsoft.Maui.Controls.Xaml.IXamlTypeResolver.Resolve(string qualifiedTypeName, System.IServiceProvider serviceProvider = null, bool expandToExtension = true) -> System.Type @@ -143,4 +146,5 @@ static readonly Microsoft.Maui.Controls.TitleBar.SubtitleProperty -> Microsoft.M static readonly Microsoft.Maui.Controls.TitleBar.TitleProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.TitleBar.TrailingContentProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty! -virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void \ No newline at end of file +virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Shipped.txt index 3792b5f9c96a..e2c9056a24a6 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Shipped.txt @@ -1681,7 +1681,7 @@ ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.AssemblyName.set -> void ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.ClrNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlNamespace.get -> string -~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) -> void +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.XmlnsDefinitionAttribute(string xmlNamespace, string target) -> void ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.Prefix.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlNamespace.get -> string ~Microsoft.Maui.Controls.XmlnsPrefixAttribute.XmlnsPrefixAttribute(string xmlNamespace, string prefix) -> void diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 4ee051439ef7..1d45b62f41dc 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -17,6 +17,7 @@ Microsoft.Maui.Controls.StyleableElement.Style.get -> Microsoft.Maui.Controls.St ~Microsoft.Maui.Controls.WebViewProcessTerminatedEventArgs.PlatformArgs.get -> Microsoft.Maui.Controls.PlatformWebViewProcessTerminatedEventArgs ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.RequireServiceAttribute(System.Type[] serviceTypes) -> void ~Microsoft.Maui.Controls.Xaml.RequireServiceAttribute.ServiceTypes.get -> System.Type[] +~Microsoft.Maui.Controls.XmlnsDefinitionAttribute.Target.get -> string ~override Microsoft.Maui.Controls.ShellContent.OnPropertyChanged(string propertyName = null) -> void *REMOVED*~static Microsoft.Maui.Controls.Application.ControlsApplicationMapper -> Microsoft.Maui.IPropertyMapper ~static Microsoft.Maui.Controls.Brush.DarkGrey.get -> Microsoft.Maui.Controls.SolidColorBrush @@ -143,4 +144,7 @@ static readonly Microsoft.Maui.Controls.TitleBar.SubtitleProperty -> Microsoft.M static readonly Microsoft.Maui.Controls.TitleBar.TitleProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.TitleBar.TrailingContentProperty -> Microsoft.Maui.Controls.BindableProperty! static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty! -virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void \ No newline at end of file +virtual Microsoft.Maui.Controls.Application.ActivateWindow(Microsoft.Maui.Controls.Window! window) -> void +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.Allow.get -> bool +Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute.AllowImplicitXmlnsDeclarationAttribute(bool allow = true) -> void diff --git a/src/Controls/src/Core/XmlnsDefinitionAttribute.cs b/src/Controls/src/Core/XmlnsDefinitionAttribute.cs index ce07d170a8f4..c84259e35986 100644 --- a/src/Controls/src/Core/XmlnsDefinitionAttribute.cs +++ b/src/Controls/src/Core/XmlnsDefinitionAttribute.cs @@ -2,25 +2,34 @@ using System; using System.Diagnostics; -namespace Microsoft.Maui.Controls +namespace Microsoft.Maui.Controls; + +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +[DebuggerDisplay("{XmlNamespace}, {Target}, {AssemblyName}")] +public sealed class XmlnsDefinitionAttribute : Attribute { - /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - [DebuggerDisplay("{XmlNamespace}, {ClrNamespace}, {AssemblyName}")] - public sealed class XmlnsDefinitionAttribute : Attribute + /// + public string XmlNamespace { get; } + + /// + public string Target { get; } + /// + [Obsolete("Use Target for ClrNamespace or other xmlns")] + public string ClrNamespace => Target; + /// + public string AssemblyName { get; set; } + + /// + public XmlnsDefinitionAttribute(string xmlNamespace, string target) { - /// - public string XmlNamespace { get; } - /// - public string ClrNamespace { get; } - /// - public string AssemblyName { get; set; } + //TODO we need an analyzer to check before runtime + if (target == "http://schemas.microsoft.com/winfx/2009/xaml" || target == "http://schemas.microsoft.com/winfx/2006/xaml") + throw new ArgumentException($"Target cannot be {target}. That namespace can't be aggregated", nameof(target)); - /// - public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace) - { - ClrNamespace = clrNamespace ?? throw new ArgumentNullException(nameof(xmlNamespace)); - XmlNamespace = xmlNamespace ?? throw new ArgumentNullException(nameof(clrNamespace)); - } + if (target.StartsWith("http", StringComparison.Ordinal) && xmlNamespace != "http://schemas.microsoft.com/dotnet/maui/global") + throw new ArgumentException($"We only support xmlns aggregation in http://schemas.microsoft.com/dotnet/maui/global", nameof(target)); + Target = target ?? throw new ArgumentNullException(nameof(target)); + XmlNamespace = xmlNamespace ?? throw new ArgumentNullException(nameof(xmlNamespace)); } } diff --git a/src/Controls/src/SourceGen/CodeBehindGenerator.cs b/src/Controls/src/SourceGen/CodeBehindGenerator.cs index 880c828a91d9..bbc4a7d0b1cf 100644 --- a/src/Controls/src/SourceGen/CodeBehindGenerator.cs +++ b/src/Controls/src/SourceGen/CodeBehindGenerator.cs @@ -39,6 +39,15 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext) // System.Diagnostics.Debugger.Launch(); //} #endif + // Only provide a new Compilation when the references change + var referenceCompilationProvider = initContext.CompilationProvider + .WithComparer(new CompilationReferencesComparer()) + .WithTrackingName(TrackingNames.ReferenceCompilationProvider); + + var xmlnsDefinitionsProvider = referenceCompilationProvider + .Select(GetAssemblyAttributes) + .WithTrackingName(TrackingNames.XmlnsDefinitionsProvider); + var projectItemProvider = initContext.AdditionalTextsProvider .Combine(initContext.AnalyzerConfigOptionsProvider) .Select(ComputeProjectItem) @@ -46,6 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext) var xamlProjectItemProvider = projectItemProvider .Where(static p => p?.Kind == "Xaml") + .Combine(xmlnsDefinitionsProvider) .Select(ComputeXamlProjectItem) .WithTrackingName(TrackingNames.XamlProjectItemProvider); @@ -53,30 +63,18 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext) .Where(static p => p?.Kind == "Css") .WithTrackingName(TrackingNames.CssProjectItemProvider); - // Only provide a new Compilation when the references change - var referenceCompilationProvider = initContext.CompilationProvider - .WithComparer(new CompilationReferencesComparer()) - .WithTrackingName(TrackingNames.ReferenceCompilationProvider); - - var xmlnsDefinitionsProvider = referenceCompilationProvider - .Select(GetAssemblyAttributes) - .WithTrackingName(TrackingNames.XmlnsDefinitionsProvider); - var referenceTypeCacheProvider = referenceCompilationProvider .Select(GetTypeCache) .WithTrackingName(TrackingNames.ReferenceTypeCacheProvider); var xamlSourceProvider = xamlProjectItemProvider - .Combine(xmlnsDefinitionsProvider) - .Combine(referenceTypeCacheProvider) - .Combine(referenceCompilationProvider) - .Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right)) + .Combine(xmlnsDefinitionsProvider, referenceTypeCacheProvider, referenceCompilationProvider) .WithTrackingName(TrackingNames.XamlSourceProvider); // Register the XAML pipeline initContext.RegisterSourceOutput(xamlSourceProvider, static (sourceProductionContext, provider) => { - var ((xamlItem, xmlnsCache), typeCache, compilation) = provider; + var (xamlItem, xmlnsCache, typeCache, compilation) = provider; GenerateXamlCodeBehind(xamlItem, compilation, sourceProductionContext, xmlnsCache, typeCache); }); @@ -91,6 +89,44 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext) GenerateCssCodeBehind(cssItem, sourceProductionContext); }); + + initContext.RegisterPostInitializationOutput(static context => + { + context.AddSource("GlobalXmlns.g.cs", SourceText.From( +$""" +{AutoGeneratedHeaderText} + +[assembly: global::Microsoft.Maui.Controls.XmlnsDefinition("{XamlParser.MauiGlobalUri}", "{XamlParser.MauiUri}")] +[assembly: global::Microsoft.Maui.Controls.XmlnsPrefix("{XamlParser.MauiGlobalUri}", "global")] + +#if MauiAllowImplicitXmlnsDeclaration +[assembly: global::Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclaration] +#endif +""" + , Encoding.UTF8)); + }); + + initContext.RegisterSourceOutput(xmlnsDefinitionsProvider, static (sourceProductionContext, xmlnsCache) => + { + var source = GenerateGlobalXmlns(sourceProductionContext, xmlnsCache); + if (!string.IsNullOrEmpty(source)) + sourceProductionContext.AddSource("Global.Xmlns.cs", SourceText.From(source!, Encoding.UTF8)); + }); + } + + private static string? GenerateGlobalXmlns(SourceProductionContext sourceProductionContext, AssemblyCaches xmlnsCache) + { + if (xmlnsCache.GlobalGeneratedXmlnsDefinitions.Count == 0) + { + return null; + } + var sb = new StringBuilder(); + sb.AppendLine(AutoGeneratedHeaderText); + foreach (var xmlns in xmlnsCache.GlobalGeneratedXmlnsDefinitions) + { + sb.AppendLine($"[assembly: global::Microsoft.Maui.Controls.XmlnsDefinition(\"{xmlns.XmlNamespace}\", \"{xmlns.Target}\", AssemblyName = \"{EscapeIdentifier(xmlns.AssemblyName)}\")]"); + } + return sb.ToString(); } static string EscapeIdentifier(string identifier) @@ -117,8 +153,10 @@ static string EscapeIdentifier(string identifier) return new ProjectItem(additionalText, targetPath: targetPath, relativePath: relativePath, manifestResourceName: manifestResourceName, kind: kind, targetFramework: targetFramework); } - static XamlProjectItem? ComputeXamlProjectItem(ProjectItem? projectItem, CancellationToken cancellationToken) + static XamlProjectItem? ComputeXamlProjectItem((ProjectItem?, AssemblyCaches) itemAndCaches, CancellationToken cancellationToken) { + var projectItem = itemAndCaches.Item1; + var xmlnsCache = itemAndCaches.Item2; if (projectItem == null) { return null; @@ -130,10 +168,25 @@ static string EscapeIdentifier(string identifier) return null; } + var allowImplicitXmlns = itemAndCaches.Item2.AllowImplicitXmlns; + + var nsmgr = new XmlNamespaceManager(new NameTable()); + nsmgr.AddNamespace("__f__", XamlParser.MauiUri); + nsmgr.AddNamespace("__g__", XamlParser.MauiGlobalUri); + if (allowImplicitXmlns) + { + nsmgr.AddNamespace("", XamlParser.DefaultImplicitUri); + foreach (var xmlnsPrefix in xmlnsCache.XmlnsPrefixes) + nsmgr.AddNamespace(xmlnsPrefix.Prefix, xmlnsPrefix.XmlNamespace); + } + using var reader = XmlReader.Create(new StringReader(text.ToString()), + new XmlReaderSettings { ConformanceLevel = allowImplicitXmlns ? ConformanceLevel.Fragment : ConformanceLevel.Document }, + new XmlParserContext(nsmgr.NameTable, nsmgr, null, XmlSpace.None)); + var xmlDoc = new XmlDocument(); try { - xmlDoc.LoadXml(text.ToString()); + xmlDoc.Load(reader); } catch (XmlException xe) { @@ -149,9 +202,6 @@ static string EscapeIdentifier(string identifier) cancellationToken.ThrowIfCancellationRequested(); - var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); - nsmgr.AddNamespace("__f__", XamlParser.MauiUri); - var root = xmlDoc.SelectSingleNode("/*", nsmgr); if (root == null) { @@ -189,6 +239,12 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio // [assembly: InternalsVisibleTo] INamedTypeSymbol? internalsVisibleToAttribute = compilation.GetTypeByMetadataName(typeof(InternalsVisibleToAttribute).FullName); + INamedTypeSymbol? xmlnsPrefixAttribute = compilation.GetTypesByMetadataName(typeof(XmlnsPrefixAttribute).FullName) + .SingleOrDefault(t => t.ContainingAssembly.Identity.Name == "Microsoft.Maui.Controls"); + + INamedTypeSymbol? allowImplicitXmlnsAttribute = compilation.GetTypesByMetadataName(typeof(Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute).FullName) + .SingleOrDefault(t => t.ContainingAssembly.Identity.Name == "Microsoft.Maui.Controls"); + if (xmlnsDefinitonAttribute is null || internalsVisibleToAttribute is null) { return AssemblyCaches.Empty; @@ -196,20 +252,32 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio var xmlnsDefinitions = new List(); var internalsVisible = new List(); - + var xmlnsPrefixes = new List(); + var allowImplicitXmlns = compilation.Assembly.GetAttributes() + .Any(a => + SymbolEqualityComparer.Default.Equals(a.AttributeClass, allowImplicitXmlnsAttribute) + && (a.ConstructorArguments.Length == 0 || a.ConstructorArguments[0].Value is bool b && b)); internalsVisible.Add(compilation.Assembly); - // load from references + IList assemblies = new List(); + assemblies.Add(compilation.Assembly); foreach (var reference in compilation.References) { cancellationToken.ThrowIfCancellationRequested(); - if (compilation.GetAssemblyOrModuleSymbol(reference) is not IAssemblySymbol symbol) + var assembly = compilation.GetAssemblyOrModuleSymbol(reference); + if (assembly is IAssemblySymbol assemblySymbol) { - continue; + assemblies.Add(assemblySymbol); } + } + + // load from references + foreach (var assembly in assemblies) + { + cancellationToken.ThrowIfCancellationRequested(); - foreach (var attr in symbol.GetAttributes()) + foreach (var attr in assembly.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, xmlnsDefinitonAttribute)) { @@ -221,7 +289,14 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio } else { - xmlnsDef.AssemblyName = symbol.Name; + xmlnsDef.AssemblyName = assembly.Name; + } + + //only add globalxmlns definition from the current assembly + if (xmlnsDef.XmlNamespace == XamlParser.MauiGlobalUri + && !SymbolEqualityComparer.Default.Equals(assembly, compilation.Assembly)) + { + continue; } xmlnsDefinitions.Add(xmlnsDef); @@ -231,12 +306,31 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio // [assembly: InternalsVisibleTo] if (attr.ConstructorArguments[0].Value is string assemblyName && new AssemblyName(assemblyName).Name == compilation.Assembly.Identity.Name) { - internalsVisible.Add(symbol); + internalsVisible.Add(assembly); } } + else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, xmlnsPrefixAttribute)) + { + // [assembly: XmlnsPrefix] + var xmlnsPrefix = new XmlnsPrefixAttribute(attr.ConstructorArguments[0].Value as string, attr.ConstructorArguments[1].Value as string); + xmlnsPrefixes.Add(xmlnsPrefix); + } + } + } + + var globalXmlns = xmlnsDefinitions.Where(x => x.XmlNamespace == XamlParser.MauiGlobalUri).ToList(); + var globalGeneratedXmlnsDefinitions = new List(); + foreach (var global in globalXmlns) + { + var pointedXmlns = xmlnsDefinitions.Where(x => x.XmlNamespace == global.Target).ToList(); + foreach (var pointed in pointedXmlns) + { + xmlnsDefinitions.Add(new XmlnsDefinitionAttribute(global.XmlNamespace, pointed.Target) { AssemblyName = pointed.AssemblyName }); + globalGeneratedXmlnsDefinitions.Add(new XmlnsDefinitionAttribute(global.XmlNamespace, pointed.Target) { AssemblyName = pointed.AssemblyName }); } } - return new AssemblyCaches(xmlnsDefinitions, internalsVisible); + + return new AssemblyCaches([.. xmlnsDefinitions.Distinct()], xmlnsPrefixes, [.. globalGeneratedXmlnsDefinitions.Distinct()], internalsVisible, allowImplicitXmlns); } static IDictionary GetTypeCache(Compilation compilation, CancellationToken cancellationToken) @@ -288,12 +382,13 @@ static void GenerateXamlCodeBehind(XamlProjectItem? xamlItem, Compilation compil if (projItem.ManifestResourceName != null && projItem.TargetPath != null) { sb.AppendLine($"[assembly: global::Microsoft.Maui.Controls.Xaml.XamlResourceId(\"{projItem.ManifestResourceName}\", \"{projItem.TargetPath.Replace('\\', '/')}\", {(rootType == null ? "null" : "typeof(global::" + rootClrNamespace + "." + rootType + ")")})]"); - } - if (XamlResourceIdOnly) - { - context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8)); - return; + + if (XamlResourceIdOnly) + { + context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8)); + return; + } } if (rootType == null) @@ -377,7 +472,7 @@ static void GenerateXamlCodeBehind(XamlProjectItem? xamlItem, Compilation compil context.AddSource(hintName, SourceText.From(sb.ToString(), Encoding.UTF8)); } - static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache, CancellationToken cancellationToken, 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) + static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache, CancellationToken cancellationToken, 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) { accessModifier = null; rootType = null; @@ -430,6 +525,8 @@ static bool TryParseXaml(XamlProjectItem parseResult, string uid, Compilation co namedFields = GetNamedFields(root, nsmgr, compilation, xmlnsCache, 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, xmlnsCache, typeCache); + if (baseType == null) + return false; // x:ClassModifier attribute var classModifier = GetAttributeValue(root, "ClassModifier", XamlParser.X2006Uri, XamlParser.X2009Uri); @@ -457,7 +554,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc) return true; } - static IEnumerable<(string name, string type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache, CancellationToken cancellationToken) + static IEnumerable<(string name, string? type, string accessModifier)> GetNamedFields(XmlNode root, XmlNamespaceManager nsmgr, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache, CancellationToken cancellationToken) { var xPrefix = nsmgr.LookupPrefix(XamlParser.X2006Uri) ?? nsmgr.LookupPrefix(XamlParser.X2009Uri); if (xPrefix == null) @@ -468,7 +565,8 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc) XmlNodeList names = root.SelectNodes( "//*[@" + xPrefix + ":Name" + - "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)]", nsmgr); + "][not(ancestor:: __f__:DataTemplate) and not(ancestor:: __f__:ControlTemplate) and not(ancestor:: __f__:Style) and not(ancestor:: __f__:VisualStateManager.VisualStateGroups)" + + "and not(ancestor:: __g__:DataTemplate) and not(ancestor:: __g__:ControlTemplate) and not(ancestor:: __g__:Style) and not(ancestor:: __g__:VisualStateManager.VisualStateGroups)]", nsmgr); foreach (XmlNode node in names) { cancellationToken.ThrowIfCancellationRequested(); @@ -492,7 +590,7 @@ static bool GetXamlCompilationProcessingInstruction(XmlDocument xmlDoc) } } - static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache) + static string? GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCaches xmlnsCache, IDictionary typeCache) { if (typeCache.TryGetValue(xmlType, out string returnType)) { @@ -515,6 +613,11 @@ static string GetTypeName(XmlType xmlType, Compilation compilation, AssemblyCach returnType = $"{returnType}<{string.Join(", ", xmlType.TypeArguments.Select(typeArg => GetTypeName(typeArg, compilation, xmlnsCache, typeCache)))}>"; } + if (returnType == null) + { + return null; + } + returnType = $"global::{returnType}"; typeCache[xmlType] = returnType; return returnType; @@ -755,17 +858,22 @@ public XamlProjectItem(ProjectItem projectItem, Exception exception) class AssemblyCaches { - public static readonly AssemblyCaches Empty = new(Array.Empty(), Array.Empty()); + public static readonly AssemblyCaches Empty = new(Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), false); - public AssemblyCaches(IReadOnlyList xmlnsDefinitions, IReadOnlyList internalsVisible) + public AssemblyCaches(IReadOnlyList xmlnsDefinitions, IReadOnlyList xmlnsPrefixes, IReadOnlyList globalGeneratedXmlnsDefinitions, IReadOnlyList internalsVisible, bool allowImplicitXmlns) { XmlnsDefinitions = xmlnsDefinitions; + XmlnsPrefixes = xmlnsPrefixes; + GlobalGeneratedXmlnsDefinitions = globalGeneratedXmlnsDefinitions; InternalsVisible = internalsVisible; + AllowImplicitXmlns = allowImplicitXmlns; } public IReadOnlyList XmlnsDefinitions { get; } - + public IReadOnlyList XmlnsPrefixes { get; } + public IReadOnlyList GlobalGeneratedXmlnsDefinitions { get; } public IReadOnlyList InternalsVisible { get; } + public bool AllowImplicitXmlns { get; } } class CompilationReferencesComparer : IEqualityComparer diff --git a/src/Controls/src/SourceGen/Controls.SourceGen.csproj b/src/Controls/src/SourceGen/Controls.SourceGen.csproj index ab0f81f64a3b..1590927936fb 100644 --- a/src/Controls/src/SourceGen/Controls.SourceGen.csproj +++ b/src/Controls/src/SourceGen/Controls.SourceGen.csproj @@ -21,6 +21,8 @@ + + diff --git a/src/Controls/src/SourceGen/IncrementalValueProviderExtensions.cs b/src/Controls/src/SourceGen/IncrementalValueProviderExtensions.cs new file mode 100644 index 000000000000..14ec8fcd35cd --- /dev/null +++ b/src/Controls/src/SourceGen/IncrementalValueProviderExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Maui.Controls.SourceGen; + +//https://github.com/dotnet/roslyn/pull/78316 +public static class IncrementalValueProviderExtensions +{ + public static IncrementalValuesProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3)> Combine(this IncrementalValuesProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3) => provider1.Combine(provider2).Combine(provider3).Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right)); + + public static IncrementalValuesProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3, TProvider4 provider4)> Combine(this IncrementalValuesProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3, IncrementalValueProvider provider4) => provider1.Combine(provider2).Combine(provider3).Combine(provider4).Select(static (t, _) => (t.Left.Left.Left, t.Left.Left.Right, t.Left.Right, t.Right)); + + public static IncrementalValuesProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3, TProvider4 provider4, TProvider5 provider5)> Combine(this IncrementalValuesProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3, IncrementalValueProvider provider4, IncrementalValueProvider provider5) => provider1.Combine(provider2).Combine(provider3).Combine(provider4).Combine(provider5).Select(static (t, _) => (t.Left.Left.Left.Left, t.Left.Left.Left.Right, t.Left.Left.Right, t.Left.Right, t.Right)); + + public static IncrementalValueProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3)> Combine(this IncrementalValueProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3) => provider1.Combine(provider2).Combine(provider3).Select(static (t, _) => (t.Left.Left, t.Left.Right, t.Right)); + + public static IncrementalValueProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3, TProvider4 provider4)> Combine(this IncrementalValueProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3, IncrementalValueProvider provider4) => provider1.Combine(provider2).Combine(provider3).Combine(provider4).Select(static (t, _) => (t.Left.Left.Left, t.Left.Left.Right, t.Left.Right, t.Right)); + + public static IncrementalValueProvider<(TProvider1 provider1, TProvider2 provider2, TProvider3 provider3, TProvider4 provider4, TProvider5 provider5)> Combine(this IncrementalValueProvider provider1, IncrementalValueProvider provider2, IncrementalValueProvider provider3, IncrementalValueProvider provider4, IncrementalValueProvider provider5) => provider1.Combine(provider2).Combine(provider3).Combine(provider4).Combine(provider5).Select(static (t, _) => (t.Left.Left.Left.Left, t.Left.Left.Left.Right, t.Left.Left.Right, t.Left.Right, t.Right)); + +} diff --git a/src/Controls/src/Xaml/XamlLoader.cs b/src/Controls/src/Xaml/XamlLoader.cs index 0fff80951c3d..82b07624cff9 100644 --- a/src/Controls/src/Xaml/XamlLoader.cs +++ b/src/Controls/src/Xaml/XamlLoader.cs @@ -59,8 +59,23 @@ public static void Load(object view, Type callingType) public static void Load(object view, string xaml, Assembly rootAssembly, bool useDesignProperties) { + rootAssembly ??= view.GetType().Assembly; + + var allowImplicitXmlns = rootAssembly.CustomAttributes.Any(a => + a.AttributeType.FullName == "Microsoft.Maui.Controls.Xaml.Internals.AllowImplicitXmlnsDeclarationAttribute" + && (a.ConstructorArguments.Count == 0 || a.ConstructorArguments[0].Value is bool b && b)); + + var nsmgr = new XmlNamespaceManager(new NameTable()); + if (allowImplicitXmlns) + { + nsmgr.AddNamespace("", XamlParser.DefaultImplicitUri); + foreach (var xmlnsPrefix in XamlParser.GetXmlnsPrefixAttributes()) + nsmgr.AddNamespace(xmlnsPrefix.Prefix, xmlnsPrefix.XmlNamespace); + } using (var textReader = new StringReader(xaml)) - using (var reader = XmlReader.Create(textReader)) + using (var reader = XmlReader.Create(textReader, + new XmlReaderSettings { ConformanceLevel = allowImplicitXmlns ? ConformanceLevel.Fragment : ConformanceLevel.Document }, + new XmlParserContext(nsmgr.NameTable, nsmgr, null, XmlSpace.None))) { while (reader.Read()) { @@ -82,7 +97,7 @@ public static void Load(object view, string xaml, Assembly rootAssembly, bool us Visit(rootnode, new HydrationContext { RootElement = view, - RootAssembly = rootAssembly ?? view.GetType().Assembly, + RootAssembly = rootAssembly, ExceptionHandler = doNotThrow ? ehandler : (Action)null }, useDesignProperties); diff --git a/src/Controls/src/Xaml/XamlParser.Namespaces.cs b/src/Controls/src/Xaml/XamlParser.Namespaces.cs index f930d2de357a..ecd055a1ab47 100644 --- a/src/Controls/src/Xaml/XamlParser.Namespaces.cs +++ b/src/Controls/src/Xaml/XamlParser.Namespaces.cs @@ -6,6 +6,8 @@ static partial class XamlParser { [Obsolete("Should not be used except for migration/error message purposes")] public const string FormsUri = "http://xamarin.com/schemas/2014/forms"; + public const string DefaultImplicitUri = MauiGlobalUri; + public const string MauiGlobalUri = "http://schemas.microsoft.com/dotnet/maui/global"; public const string MauiUri = "http://schemas.microsoft.com/dotnet/2021/maui"; public const string MauiDesignUri = "http://schemas.microsoft.com/dotnet/2021/maui/design"; public const string X2006Uri = "http://schemas.microsoft.com/winfx/2006/xaml"; diff --git a/src/Controls/src/Xaml/XamlParser.cs b/src/Controls/src/Xaml/XamlParser.cs index 242b98b19941..0934e9f488d2 100644 --- a/src/Controls/src/Xaml/XamlParser.cs +++ b/src/Controls/src/Xaml/XamlParser.cs @@ -99,8 +99,8 @@ static void ParseXamlElementFor(IElementNode node, XmlReader reader) node.Properties.Add(XmlName.xArguments, prop); } // 3. DataTemplate (should be handled by 4.) - else if (node.XmlType.NamespaceUri == MauiUri && - (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate")) + else if ((node.XmlType.NamespaceUri == MauiUri || node.XmlType.NamespaceUri == MauiGlobalUri) + && (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate")) { if (node.Properties.ContainsKey(XmlName._CreateContent)) throw new XamlParseException($"Multiple child elements in {node.XmlType.Name}", ((IXmlLineInfo)reader).Clone()); @@ -333,10 +333,25 @@ static IValueNode GetValueNode(object value, XmlReader reader) static IList s_xmlnsDefinitions; - static void GatherXmlnsDefinitionAttributes() + static bool ValidateProtectedXmlns(string xmlNamespace, string assemblyName) + { + //maui, and x: xmlns are protected + if (xmlNamespace != XamlParser.MauiUri && xmlNamespace != XamlParser.X2009Uri) + return true; + + //we know thos assemblies, they are fine in maui or x xmlns + if (assemblyName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) + || assemblyName.StartsWith("System", StringComparison.OrdinalIgnoreCase) + || assemblyName.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + static void GatherXmlnsDefinitionAttributes(Assembly currentAssembly) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - s_xmlnsDefinitions = new List(); + s_xmlnsDefinitions = []; foreach (var assembly in assemblies) { @@ -344,8 +359,19 @@ static void GatherXmlnsDefinitionAttributes() { foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute))) { + // Only add global xmlns definition from the current assembly + if (attribute.XmlNamespace == XamlParser.MauiGlobalUri + && assembly != currentAssembly) + continue; + + attribute.AssemblyName ??= assembly.FullName; + + if (!ValidateProtectedXmlns(attribute.XmlNamespace, attribute.AssemblyName)) + { + Debug.WriteLine($"Can not overloadxmlns {attribute.XmlNamespace}. cause it's protected."); + continue; + } s_xmlnsDefinitions.Add(attribute); - attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName; } } catch (Exception ex) @@ -357,6 +383,14 @@ static void GatherXmlnsDefinitionAttributes() } } + public static IList GetXmlnsPrefixAttributes() + { + return [.. AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetCustomAttributes(typeof(XmlnsPrefixAttribute), false) + .OfType()).Distinct()]; + } + + [RequiresUnreferencedCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)] #if !NETSTANDARD [RequiresDynamicCode(TrimmerConstants.XamlRuntimeParsingNotSupportedWarning)] @@ -368,9 +402,9 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl retry: if (s_xmlnsDefinitions == null) - GatherXmlnsDefinitionAttributes(); + GatherXmlnsDefinitionAttributes(currentAssembly); - Type type = xmlType.GetTypeReference( + IEnumerable types = xmlType.GetTypeReferences( s_xmlnsDefinitions, currentAssembly?.FullName, (typeInfo) => @@ -385,7 +419,7 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl var typeArguments = xmlType.TypeArguments; exception = null; - if (type == null) + if (!types.Any()) { // This covers the scenario where the AppDomain's loaded // assemblies might have changed since this method was first @@ -399,6 +433,14 @@ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembl } } + if (types.Distinct().Skip(1).Any()) + { + exception = new XamlParseException($"Ambiguous type '{xmlType.Name}' in xmlns '{xmlType.NamespaceUri}'", xmlInfo); + return null; + } + + var type = types.Distinct().FirstOrDefault(); + if (type != null && typeArguments != null) { XamlParseException innerexception = null; diff --git a/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs b/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs index 3b681ff1e755..ee212eea76ac 100644 --- a/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs +++ b/src/Controls/src/Xaml/XmlTypeXamlExtensions.cs @@ -28,12 +28,21 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.Maui.Controls.Xaml { static class XmlTypeXamlExtensions { public static T? GetTypeReference( + this XmlType xmlType, + IEnumerable xmlnsDefinitions, + string defaultAssemblyName, + Func<(string typeName, string clrNamespace, string assemblyName), T> refFromTypeInfo, + bool expandToExtension = true) + where T : class => GetTypeReferences(xmlType, xmlnsDefinitions, defaultAssemblyName, refFromTypeInfo, expandToExtension).FirstOrDefault(); + + public static IEnumerable GetTypeReferences( this XmlType xmlType, IEnumerable xmlnsDefinitions, string defaultAssemblyName, @@ -82,23 +91,26 @@ static class XmlTypeXamlExtensions { foreach (XmlnsDefinitionAttribute xmlnsDefinitionAttribute in lookupAssemblies) { - potentialTypes.Add(new(typeName, xmlnsDefinitionAttribute.ClrNamespace, xmlnsDefinitionAttribute.AssemblyName)); + potentialTypes.Add(new(typeName, xmlnsDefinitionAttribute.Target, xmlnsDefinitionAttribute.AssemblyName)); // As a fallback, for assembly=mscorlib try assembly=System.Private.CoreLib if (xmlnsDefinitionAttribute.AssemblyName is string assemblyName && (assemblyName == "mscorlib" || assemblyName.StartsWith("mscorlib,", StringComparison.Ordinal))) { - potentialTypes.Add(new(typeName, xmlnsDefinitionAttribute.ClrNamespace, "System.Private.CoreLib")); + potentialTypes.Add(new(typeName, xmlnsDefinitionAttribute.Target, "System.Private.CoreLib")); } } } T? type = null; + string? returnTypeName = null; foreach (var typeInfo in potentialTypes) - if ((type = refFromTypeInfo(typeInfo)) != null) - break; - - return type; + if ((returnTypeName == null || returnTypeName == typeInfo.typeName) //only return multiple types if they share the same name. avoid returning both BindingExtension and Binding + && (type = refFromTypeInfo(typeInfo)) != null) + { + returnTypeName = typeInfo.typeName; + yield return type; + } } } } \ 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 ff9f8b9242fa..799a1b4cb000 100644 --- a/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs @@ -30,7 +30,7 @@ public void TestCodeBehindGenerator_BasicCss() Assert.IsFalse(result.Diagnostics.Any()); - var generated = result.Results.Single().GeneratedSources.Single().SourceText.ToString(); + var generated = result.Results.Single().GeneratedSources.Single(gs => gs.HintName.EndsWith(".sg.cs", StringComparison.OrdinalIgnoreCase)).SourceText.ToString(); Assert.IsTrue(generated.Contains($"XamlResourceId(\"{cssFile.ManifestResourceName}\", \"{cssFile.Path}\"", StringComparison.Ordinal)); } @@ -58,10 +58,10 @@ public void TestCodeBehindGenerator_ModifiedCss() var result1 = result.result1.Results.Single(); var result2 = result.result2.Results.Single(); - var output1 = result1.GeneratedSources.Single().SourceText.ToString(); - var output2 = result2.GeneratedSources.Single().SourceText.ToString(); + var output1 = result1.GeneratedSources.Single(gs => gs.HintName.EndsWith(".sg.cs", StringComparison.OrdinalIgnoreCase)).SourceText.ToString(); + var output2 = result2.GeneratedSources.Single(gs => gs.HintName.EndsWith(".sg.cs", StringComparison.OrdinalIgnoreCase)).SourceText.ToString(); - Assert.IsTrue(result1.TrackedSteps.All(s => s.Value.Single().Outputs.Single().Reason == IncrementalStepRunReason.New)); + // Assert.IsTrue(result1.TrackedSteps.All(s => s.Value.Single().Outputs.Single().Reason == IncrementalStepRunReason.New)); Assert.AreEqual(output1, output2); Assert.IsTrue(output1.Contains($"XamlResourceId(\"{cssFile.ManifestResourceName}\", \"{cssFile.Path}\"", StringComparison.Ordinal)); diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs index 4c18f47858f9..08a76178d5fe 100644 --- a/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.Maui.Controls.SourceGen; using NUnit.Framework; @@ -32,11 +33,63 @@ public void TestCodeBehindGenerator_BasicXaml() Assert.IsFalse(result.Diagnostics.Any()); - var generated = result.Results.Single().GeneratedSources.Single().SourceText.ToString(); + var generated = result.Results.Single().GeneratedSources.Single(gs => gs.HintName.EndsWith(".sg.cs", StringComparison.OrdinalIgnoreCase)).SourceText.ToString(); Assert.IsTrue(generated.Contains("Microsoft.Maui.Controls.Button MyButton", StringComparison.Ordinal)); } + [Test] + public void TestCodeBehindGenerator_GlobalNamespace() + { + var xaml = +""" + + +