Skip to content

Commit faf5abd

Browse files
authored
Merge pull request #19692 from unoplatform/dev/cdb/hd/xaml-sources-gen-escaping
feat: Added ability to generate code that doesn't blow up raw string litterals
2 parents 41d18b5 + 088392e commit faf5abd

12 files changed

+660
-2
lines changed

src/SamplesApp/Directory.Build.props

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<PropertyGroup>
77
<!-- fixed for self-testing inside the Uno Solution -->
88
<UnoRemoteControlPort>53487</UnoRemoteControlPort>
9+
<UnoGenerateXamlSourcesProvider>True</UnoGenerateXamlSourcesProvider>
910
</PropertyGroup>
1011

1112
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using Uno.UI.SourceGenerators.Tests.Verifiers;
2+
3+
namespace Uno.UI.SourceGenerators.Tests.XamlCodeGeneratorTests;
4+
5+
using Verify = XamlSourceGeneratorVerifier;
6+
7+
[TestClass]
8+
public class Given_GenerateEmbeddedXamlSources
9+
{
10+
[TestMethod]
11+
public async Task EscapeTripleQuotes()
12+
{
13+
var xamlFile = new XamlFile("MainPage.xaml", """"""
14+
<Page x:Class="TestRepro.MainPage"
15+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
16+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
17+
<!-- This is a comment having potential escaping problems: """ -->
18+
<!-- This is a comment having potential escaping problems: """ -->
19+
</Page>
20+
"""""");
21+
22+
var configOverride = new Dictionary<string, string> { { "build_property.UnoGenerateXamlSourcesProvider", "true" } };
23+
24+
var test = new Verify.Test(xamlFile)
25+
{
26+
TestState =
27+
{
28+
Sources =
29+
{
30+
"""
31+
using Microsoft.UI.Xaml;
32+
using Microsoft.UI.Xaml.Controls;
33+
34+
namespace TestRepro
35+
{
36+
public sealed partial class MainPage : Page
37+
{
38+
public MainPage()
39+
{
40+
this.InitializeComponent();
41+
}
42+
}
43+
}
44+
"""
45+
}
46+
},
47+
ReferenceAssemblies = _Dotnet.Current.WithUnoPackage(),
48+
DisableBuildReferences = true,
49+
GlobalConfigOverride = configOverride,
50+
}.AddGeneratedSources();
51+
52+
await test.RunAsync();
53+
}
54+
55+
[TestMethod]
56+
public async Task EscapeNQuotes()
57+
{
58+
var xamlFile = new XamlFile("MainPage.xaml", """"""
59+
<Page x:Class="TestRepro.MainPage"
60+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
61+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
62+
<!-- This is a comment having potential escaping problems: " "" """ """" """"" -->
63+
<!-- This is a comment having potential escaping problems: " "" """ """" """"" -->
64+
</Page>
65+
"""""");
66+
67+
var configOverride = new Dictionary<string, string> { { "build_property.UnoGenerateXamlSourcesProvider", "true" } };
68+
69+
var test = new Verify.Test(xamlFile)
70+
{
71+
TestState =
72+
{
73+
Sources =
74+
{
75+
"""
76+
using Microsoft.UI.Xaml;
77+
using Microsoft.UI.Xaml.Controls;
78+
79+
namespace TestRepro
80+
{
81+
public sealed partial class MainPage : Page
82+
{
83+
public MainPage()
84+
{
85+
this.InitializeComponent();
86+
}
87+
}
88+
}
89+
"""
90+
}
91+
},
92+
ReferenceAssemblies = _Dotnet.Current.WithUnoPackage(),
93+
DisableBuildReferences = true,
94+
GlobalConfigOverride = configOverride,
95+
}.AddGeneratedSources();
96+
97+
await test.RunAsync();
98+
}
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#nullable enable
2+
// <autogenerated />
3+
#pragma warning disable // Disable all warnings for this generated file
4+
5+
// Register an embedded sources provider for Hot Reload
6+
[assembly: global::System.Reflection.AssemblyMetadata("Uno.HotDesign.HotReloadEmbeddedXamlSourceFilesProvider", "MyProject.__Sources__.EmbeddedXamlSourcesProvider")]
7+
8+
namespace MyProject.__Sources__;
9+
10+
/// <summary>
11+
/// Provides access to the embedded XAML sources
12+
/// </summary>
13+
/// <remarks>
14+
/// This class is used to provide the embedded XAML sources to the Hot Reload engine.
15+
/// This is not intended to be used directly by application code.
16+
/// WON'T BE GENERATED ON RELEASE BUILDS
17+
/// </remarks>
18+
internal static class EmbeddedXamlSourcesProvider
19+
{
20+
// key=absolute file path
21+
private static global::System.Collections.Generic.IDictionary<string, (string ActualPath, global::System.Func<(string Hash, string Payload)> Getter)>? _XamlSources;
22+
23+
// hash of all the paths
24+
private static volatile string? _filesListHash;
25+
26+
// get the current value of the update counter
27+
private static volatile uint _updateCounter;
28+
29+
private static readonly global::System.Text.Encoding _utf8 = global::System.Text.Encoding.UTF8;
30+
31+
// The content of this method only changes when the file list changes
32+
private static global::System.Collections.Generic.IDictionary<string, (string ActualPath, global::System.Func<(string Hash, string Payload)> Getter)> EnsureInitialize()
33+
{
34+
const string currentListHash = "d6cd66944958ced0c513e0a04797b51d"; // that's the hash of all the paths, used to detect changes in the file list following a HR operation
35+
36+
// Determine if the sources have been updated or not initialized yet
37+
var previousHashList = _XamlSources;
38+
var needsUpdate = previousHashList is null || _filesListHash != currentListHash;
39+
40+
if (needsUpdate)
41+
{
42+
var xamlSources = new global::System.Collections.Generic.Dictionary<string, (string ActualPath, global::System.Func<(string Hash, string Payload)> Getter)>(1, global::System.StringComparer.OrdinalIgnoreCase);
43+
44+
// Use method groups to avoid closure allocation and ensure no lambda is created, to allow proper HR support
45+
xamlSources[NormalizePath(@"C:/Project/0/MainPage.xaml")] = (NormalizePath(@"C:/Project/0/MainPage.xaml"), GetSources_MainPage_d6cd66944958ced0c513e0a04797b51d);
46+
47+
if (global::System.Threading.Interlocked.CompareExchange(ref _XamlSources, xamlSources, previousHashList) == previousHashList)
48+
{
49+
// The sources were updated successfully (no other thread modified them concurrently)
50+
_filesListHash = currentListHash;
51+
_updateCounter++;
52+
}
53+
}
54+
55+
return _XamlSources;
56+
}
57+
58+
/// <summary>
59+
/// Gets the current update counter, used to detect changes in the sources.
60+
/// </summary>
61+
/// <remarks>
62+
/// This counter is incremented each time a Hot Reload sources update is detected.
63+
/// </remarks>
64+
public static uint UpdateCounter
65+
{
66+
get
67+
{
68+
EnsureInitialize();
69+
return _updateCounter;
70+
}
71+
}
72+
73+
public static global::System.Collections.Generic.IReadOnlyList<string> GetXamlFilesList() => [.. EnsureInitialize().Keys];
74+
75+
public static string? GetNormalizedFileName(string path)
76+
{
77+
// Will return the normalized path if the file exists, or null if it doesn't.
78+
// (the returned value will be constant for the file and can be used in a dictionary using an ordinal comparer)
79+
var normalizedPath = NormalizePath(path);
80+
return EnsureInitialize().TryGetValue(normalizedPath, out var entry) ? entry.ActualPath : null;
81+
}
82+
83+
public static (string ActualPath, string Hash, string Payload)? GetXamlFile(string path)
84+
{
85+
var normalizedPath = NormalizePath(path);
86+
if (EnsureInitialize().TryGetValue(normalizedPath, out var entry))
87+
{
88+
var sources = entry.Getter();
89+
return (entry.ActualPath, sources.Hash, sources.Payload);
90+
}
91+
return null;
92+
}
93+
94+
private static string NormalizePath(string path) => path.Replace('\\', '/');
95+
96+
#region Sources for C:/Project/0/MainPage.xaml
97+
private static (string hash, string payload) GetSources_MainPage_d6cd66944958ced0c513e0a04797b51d()
98+
{
99+
return (
100+
"1c06c16d398ea19bdc8fe30e7d2ba2dcb58578ab", // hash
101+
_utf8.GetString(""""""
102+
<Page x:Class="TestRepro.MainPage"
103+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
104+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
105+
<!-- This is a comment having potential escaping problems: " "" """ """" """"" -->
106+
<!-- This is a comment having potential escaping problems: " "" """ """" """"" -->
107+
</Page>
108+
""""""u8)); // Stored as UTF8 to minimize impact on assembly size / limitations
109+
}
110+
#endregion
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// <autogenerated />
2+
namespace MyProject
3+
{
4+
/// <summary>
5+
/// Contains all the static resources defined for the application
6+
/// </summary>
7+
public sealed partial class GlobalStaticResources
8+
{
9+
static bool _initialized;
10+
private static bool _stylesRegistered;
11+
private static bool _dictionariesRegistered;
12+
internal static global::Uno.UI.Xaml.XamlParseContext __ParseContext_ { get; } = new global::Uno.UI.Xaml.XamlParseContext()
13+
{
14+
AssemblyName = "TestProject",
15+
}
16+
;
17+
18+
static GlobalStaticResources()
19+
{
20+
Initialize();
21+
}
22+
public static void Initialize()
23+
{
24+
if (!_initialized)
25+
{
26+
_initialized = true;
27+
global::Uno.UI.GlobalStaticResources.Initialize();
28+
global::Uno.UI.Toolkit.GlobalStaticResources.Initialize();
29+
global::Uno.UI.GlobalStaticResources.RegisterDefaultStyles();
30+
global::Uno.UI.Toolkit.GlobalStaticResources.RegisterDefaultStyles();
31+
global::Uno.UI.GlobalStaticResources.RegisterResourceDictionariesBySource();
32+
global::Uno.UI.Toolkit.GlobalStaticResources.RegisterResourceDictionariesBySource();
33+
}
34+
}
35+
public static void RegisterDefaultStyles()
36+
{
37+
if(!_stylesRegistered)
38+
{
39+
_stylesRegistered = true;
40+
RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d();
41+
}
42+
}
43+
// Register ResourceDictionaries using ms-appx:/// syntax, this is called for external resources
44+
public static void RegisterResourceDictionariesBySource()
45+
{
46+
if(!_dictionariesRegistered)
47+
{
48+
_dictionariesRegistered = true;
49+
}
50+
}
51+
// Register ResourceDictionaries using ms-resource:/// syntax, this is called for local resources
52+
internal static void RegisterResourceDictionariesBySourceLocal()
53+
{
54+
}
55+
static partial void RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d();
56+
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// <auto-generated />
2+
[assembly: global::System.Reflection.AssemblyMetadata("UnoHasLocalizationResources", "False")]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// <autogenerated />
2+
#pragma warning disable CS0114
3+
#pragma warning disable CS0108
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Linq;
8+
using Uno.UI;
9+
using Uno.UI.Xaml;
10+
using Microsoft.UI.Xaml;
11+
using Microsoft.UI.Xaml.Controls;
12+
using Microsoft.UI.Xaml.Controls.Primitives;
13+
using Microsoft.UI.Xaml.Data;
14+
using Microsoft.UI.Xaml.Documents;
15+
using Microsoft.UI.Xaml.Media;
16+
using Microsoft.UI.Xaml.Media.Animation;
17+
using Microsoft.UI.Xaml.Shapes;
18+
using Windows.UI.Text;
19+
using Uno.Extensions;
20+
using Uno;
21+
using Uno.UI.Helpers;
22+
using Uno.UI.Helpers.Xaml;
23+
using MyProject;
24+
25+
#if __ANDROID__
26+
using _View = Android.Views.View;
27+
#elif __IOS__
28+
using _View = UIKit.UIView;
29+
#elif __MACOS__
30+
using _View = AppKit.NSView;
31+
#else
32+
using _View = Microsoft.UI.Xaml.UIElement;
33+
#endif
34+
35+
namespace TestRepro
36+
{
37+
partial class MainPage : global::Microsoft.UI.Xaml.Controls.Page
38+
{
39+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
40+
private const string __baseUri_prefix_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/";
41+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
42+
private const string __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/";
43+
private global::Microsoft.UI.Xaml.NameScope __nameScope = new global::Microsoft.UI.Xaml.NameScope();
44+
private void InitializeComponent()
45+
{
46+
NameScope.SetNameScope(this, __nameScope);
47+
var __that = this;
48+
base.IsParsing = true;
49+
// Source 0\MainPage.xaml (Line 1:2)
50+
;
51+
52+
this
53+
.MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(__p1 =>
54+
{
55+
// Source 0\MainPage.xaml (Line 1:2)
56+
57+
// WARNING Property __p1.base does not exist on {http://schemas.microsoft.com/winfx/2006/xaml/presentation}Page, the namespace is http://www.w3.org/XML/1998/namespace. This error was considered irrelevant by the XamlFileGenerator
58+
}
59+
))
60+
.MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(__p1 =>
61+
{
62+
// Class TestRepro.MainPage
63+
global::Uno.UI.FrameworkElementHelper.SetBaseUri(__p1, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d);
64+
__p1.CreationComplete();
65+
}
66+
))
67+
;
68+
OnInitializeCompleted();
69+
70+
}
71+
partial void OnInitializeCompleted();
72+
}
73+
}
74+
namespace MyProject
75+
{
76+
static class MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions
77+
{
78+
public delegate void XamlApplyHandler0(global::Microsoft.UI.Xaml.Controls.Page instance);
79+
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
80+
public static global::Microsoft.UI.Xaml.Controls.Page MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.Page instance, XamlApplyHandler0 handler)
81+
{
82+
handler(instance);
83+
return instance;
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)