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/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..8e94c2e08b41 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,24 @@ 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()); + 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 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 +89,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..1702ef5b0b06 100644 --- a/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs +++ b/src/Controls/src/Build.Tasks/XmlTypeExtensions.cs @@ -109,5 +109,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.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt index b0faa838339d..4217c3a95b74 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,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/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 9089f402c400..83797728a590 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,6 @@ 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 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..82b77a722fcf 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,7 @@ 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 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..586931c98f17 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,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/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index c2190ccab4e5..b9cca655bb65 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,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/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt index 0950e21053b6..3cbdc110dda9 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 diff --git a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt index 4ee051439ef7..586931c98f17 100644 --- a/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt @@ -143,4 +143,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/SourceGen/CodeBehindGenerator.cs b/src/Controls/src/SourceGen/CodeBehindGenerator.cs index 880c828a91d9..8401cabd3d07 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,22 @@ 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)); + }); } static string EscapeIdentifier(string identifier) @@ -117,8 +131,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 +146,24 @@ static string EscapeIdentifier(string identifier) return null; } + var allowImplicitXmlns = itemAndCaches.Item2.AllowImplicitXmlns; + + var nsmgr = new XmlNamespaceManager(new NameTable()); + nsmgr.AddNamespace("__f__", XamlParser.MauiUri); + 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 +179,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 +216,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,7 +229,11 @@ 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 @@ -234,9 +271,15 @@ static AssemblyCaches GetAssemblyAttributes(Compilation compilation, Cancellatio internalsVisible.Add(symbol); } } + 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); + } } } - return new AssemblyCaches(xmlnsDefinitions, internalsVisible); + return new AssemblyCaches(xmlnsDefinitions, xmlnsPrefixes, internalsVisible, allowImplicitXmlns); } static IDictionary GetTypeCache(Compilation compilation, CancellationToken cancellationToken) @@ -288,12 +331,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 +421,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 +474,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 +503,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) @@ -492,7 +538,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 +561,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 +806,20 @@ 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(), false); - public AssemblyCaches(IReadOnlyList xmlnsDefinitions, IReadOnlyList internalsVisible) + public AssemblyCaches(IReadOnlyList xmlnsDefinitions, IReadOnlyList xmlnsPrefixes, IReadOnlyList internalsVisible, bool allowImplicitXmlns) { XmlnsDefinitions = xmlnsDefinitions; + XmlnsPrefixes = xmlnsPrefixes; InternalsVisible = internalsVisible; + AllowImplicitXmlns = allowImplicitXmlns; } public IReadOnlyList XmlnsDefinitions { get; } - + public IReadOnlyList XmlnsPrefixes { 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..300fe9976b31 100644 --- a/src/Controls/src/Xaml/XamlLoader.cs +++ b/src/Controls/src/Xaml/XamlLoader.cs @@ -59,8 +59,24 @@ 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()); + nsmgr.AddNamespace("__f__", XamlParser.MauiUri); + 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 +98,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..07e2778ca4ec 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 = MauiUri; + 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..f05ce5ad192e 100644 --- a/src/Controls/src/Xaml/XamlParser.cs +++ b/src/Controls/src/Xaml/XamlParser.cs @@ -357,6 +357,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)] diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGenCssTests.cs index ff9f8b9242fa..3d4a41bd699f 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,8 +58,8 @@ 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.AreEqual(output1, output2); diff --git a/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs b/src/Controls/tests/SourceGen.UnitTests/SourceGenXamlTests.cs index 4c18f47858f9..9004d3cb53f5 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,7 +33,28 @@ 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 = +""" + + +