From 16737cc582b54ab59b850a1d66467f271824cc40 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Sep 2026 11:10:53 +0200 Subject: [PATCH 1/6] Convert BAML to XAML when exporting a project from the command line A WPF assembly keeps its windows and pages as BAML, so a project exported without converting them back is missing the parts that make it a WPF application - and the reader has no XAML to look at either. The CLI could do the conversion since --decompile-baml was added, but only if asked, which meant that everything the project exporter learned about WPF (Page and ApplicationDefinition items, resources, generated members removed from the code-behind) was invisible to anyone following issue #2253 from the command line. The flag is kept and ignored: it is documented and scripted against, and asking for what is now the default has to keep working. The test fixture is a real .g.resources container rather than a directly embedded .baml stream, because only entries inside a container reach WriteResourceToFile - a standalone .baml is copied out untouched, which is worth its own look. Assisted-by: Claude:claude-opus-5:Claude Code --- .../ICSharpCode.ILSpyCmd.Tests.csproj | 6 ++ .../ProjectExportBamlTests.cs | 94 ++++++++++++++++++ .../fixtures/test.g.resources | Bin 0 -> 289 bytes ICSharpCode.ILSpyCmd/IlspyCmdProgram.cs | 27 ++--- ICSharpCode.ILSpyCmd/README.md | 8 +- 5 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 ICSharpCode.ILSpyCmd.Tests/ProjectExportBamlTests.cs create mode 100644 ICSharpCode.ILSpyCmd.Tests/fixtures/test.g.resources diff --git a/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj b/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj index 6f9f4528b..d1179b197 100644 --- a/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj +++ b/ICSharpCode.ILSpyCmd.Tests/ICSharpCode.ILSpyCmd.Tests.csproj @@ -24,4 +24,10 @@ + + + + + diff --git a/ICSharpCode.ILSpyCmd.Tests/ProjectExportBamlTests.cs b/ICSharpCode.ILSpyCmd.Tests/ProjectExportBamlTests.cs new file mode 100644 index 000000000..08f1a886b --- /dev/null +++ b/ICSharpCode.ILSpyCmd.Tests/ProjectExportBamlTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2026 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +using NUnit.Framework; + +using static ICSharpCode.ILSpyCmd.Tests.CliTestRunner; + +namespace ICSharpCode.ILSpyCmd.Tests +{ + /// + /// This assembly carries "mainwindow.baml" as an embedded resource, so exporting it as a + /// project exercises what happens to a BAML stream on the way into the project. + /// + [TestFixture] + public class ProjectExportBamlTests + { + static readonly string testAssemblyPath = typeof(ProjectExportBamlTests).Assembly.Location; + + string outputDirectory; + + [SetUp] + public void SetUp() + { + outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(outputDirectory); + } + + [TearDown] + public void TearDown() + { + if (Directory.Exists(outputDirectory)) + Directory.Delete(outputDirectory, recursive: true); + } + + string ProjectFileContent() + { + string projectFile = Directory.EnumerateFiles(outputDirectory, "*.csproj").Single(); + return File.ReadAllText(projectFile); + } + + [Test] + public async Task BamlBecomesXamlWithoutAskingForIt() + { + var result = await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory); + + Assert.That(result.ExitCode, Is.EqualTo(0), result.Error); + string xamlFile = Path.Combine(outputDirectory, "mainwindow.xaml"); + Assert.That(File.Exists(xamlFile), Is.True, "the BAML resource is exported as XAML"); + Assert.That(File.ReadAllText(xamlFile), Does.Contain("Hello from BAML")); + } + + [Test] + public async Task TheProjectNamesTheXamlAsAPage() + { + await RunAsync(testAssemblyPath, "--disable-updatecheck", "-p", "-o", outputDirectory); + + string project = ProjectFileContent(); + Assert.Multiple(() => { + Assert.That(project, Does.Contain("is@O1_p+SK%5g?SzMBus~417oL^d$oLUTL1*ImYq!#HYR*8GxXUf^%t3Noi54ZC+|=Nl{{sjzU0bQch;FcWPxwes*e}ZIZcpqG__J znW3ezNveT`r81^vrFkWpxv4PQgHubGfQ|w=4g>-mT|kKMSd%u0dmo79av2gCG8yt1 z%E4p`Lq3qD$B+aR&1J{|sbWyH1rj_!?8^|$;Kbkvq", "Extract a single resource by name (as printed by --list-resources). Resources whose name ends with '.baml' are decompiled to XAML.", CommandOptionType.SingleValue)] public string ResourceName { get; } - [Option("--decompile-baml", "When used with -p, decompile BAML resources to XAML files (Page items) instead of leaving them as raw byte streams.", CommandOptionType.NoValue)] + [Option("--decompile-baml", "Deprecated: -p decompiles BAML resources to XAML files (Page items) on its own. Accepted so that existing scripts keep working.", CommandOptionType.NoValue)] public bool DecompileBamlFlag { get; } [Option("--dump-table ", "Dump a metadata table: prints RID, token, names, heap offsets and coded indexes of every row.
is the ECMA-335 table name (e.g. TypeDef, Property, MethodSemantics; case-insensitive) or table number (decimal or 0x-prefixed hex, e.g. 0x17).", CommandOptionType.SingleValue)] @@ -795,19 +795,14 @@ Examples: } var settings = GetSettings(module); var debugInfo = TryLoadPDB(module); - WholeProjectDecompiler decompiler; - if (DecompileBamlFlag) - { - var bamlTypeSystem = new BamlDecompilerTypeSystem(module, resolver); - var bamlSettings = new BamlDecompilerSettings { - ThrowOnAssemblyResolveErrors = settings.ThrowOnAssemblyResolveErrors - }; - decompiler = new BamlAwareWholeProjectDecompiler(settings, resolver, resolver, debugInfo, bamlTypeSystem, bamlSettings); - } - else - { - decompiler = new WholeProjectDecompiler(settings, resolver, null, resolver, debugInfo); - } + // A WPF assembly keeps its XAML as BAML, so a project exported without converting it + // back is missing every window and page it is made of. + var bamlTypeSystem = new BamlDecompilerTypeSystem(module, resolver); + var bamlSettings = new BamlDecompilerSettings { + ThrowOnAssemblyResolveErrors = settings.ThrowOnAssemblyResolveErrors + }; + WholeProjectDecompiler decompiler = new BamlAwareWholeProjectDecompiler(settings, resolver, resolver, + debugInfo, bamlTypeSystem, bamlSettings); ProjectId projectId; using (var projectFileWriter = new StreamWriter(File.Create(projectFileName))) projectId = decompiler.DecompileProject(module, Path.GetDirectoryName(projectFileName), projectFileWriter); diff --git a/ICSharpCode.ILSpyCmd/README.md b/ICSharpCode.ILSpyCmd/README.md index 10dda270a..3a42ae5be 100644 --- a/ICSharpCode.ILSpyCmd/README.md +++ b/ICSharpCode.ILSpyCmd/README.md @@ -39,8 +39,8 @@ Options: containers are listed individually as '/'. --resource Extract a single resource by name (as printed by --list-resources). Resources whose name ends with '.baml' are decompiled to XAML. - --decompile-baml When used with -p, decompile BAML resources to XAML files (Page items) instead - of leaving them as raw byte streams. + --decompile-baml Deprecated: -p decompiles BAML resources to XAML files (Page items) on its + own. Accepted so that existing scripts keep working. --dump-table
Dump a metadata table: prints RID, token, names, heap offsets and coded indexes of every row.
is the ECMA-335 table name (e.g. TypeDef, Property, MethodSemantics; case-insensitive) or table number (decimal or @@ -121,8 +121,8 @@ Examples: Extract a single resource. If the name ends with .baml, the output is decompiled XAML; otherwise raw bytes. ilspycmd sample.dll --resource sample.g.resources/mainwindow.baml -o c:\decompiled - Decompile assembly as a compilable project and convert all BAML resources to XAML Page items. - ilspycmd sample.dll -p -o c:\decompiled --decompile-baml + Decompile assembly as a compilable project. BAML resources become XAML Page items. + ilspycmd sample.dll -p -o c:\decompiled ``` ## Generate HTML diagrammers From d8dfbec04b82b034dab8410ec2fbc4d154915a4d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Sep 2026 11:19:13 +0200 Subject: [PATCH 2/6] Write XAML a XAML parser can read back Two of the defects reported on issue #2253 come from the decompiler writing text that means something else when it is read again. A markup extension is written as a single attribute value, and its grammar gives ',' '=' '{' '}' and the quote characters a meaning. Values went out unquoted, so an argument carrying any of them was read back as further name/value pairs: {DXBinding Expr='Price - Prev > 0 ? ...'}, the reported case, no longer compiles at all (MC3042, MC3045). Values without such a character stay unquoted, because quoting them would rewrite every document that never needed it. A clr-namespace declaration names the CLR namespace it maps, but nothing read that name out of it, so no lookup by namespace could match a declaration the document itself had made. Every type in such a namespace then got a second prefix declared on the element that used it. The assembly is the second half of the same lookup, and there the document records the name it was written against while a well-known type carries the assembly it resolves to now - "mscorlib" against "System.Private.CoreLib" - so the two are also accepted as the same when the recorded assembly forwards the type. Assisted-by: Claude:claude-opus-5:Claude Code --- .../Handlers/Records/XmlnsPropertyHandler.cs | 12 +- .../Xaml/NamespaceMap.cs | 35 +++++ .../Xaml/XamlExtension.cs | 2 +- ICSharpCode.BamlDecompiler/Xaml/XamlType.cs | 4 +- ICSharpCode.BamlDecompiler/Xaml/XamlUtils.cs | 79 +++++++++++ ICSharpCode.BamlDecompiler/XamlContext.cs | 2 +- ICSharpCode.BamlDecompiler/XmlnsDictionary.cs | 10 +- .../Cases/EscapeSequence.xaml | 6 +- .../Cases/MarkupExtension.xaml | 2 +- .../ILSpy.BamlDecompiler.Tests.csproj | 2 + .../MarkupExtensionQuotingTests.cs | 133 ++++++++++++++++++ .../XmlnsDeclarationPlacementTests.cs | 118 ++++++++++++++++ 12 files changed, 390 insertions(+), 15 deletions(-) create mode 100644 ILSpy.BamlDecompiler.Tests/MarkupExtensionQuotingTests.cs create mode 100644 ILSpy.BamlDecompiler.Tests/XmlnsDeclarationPlacementTests.cs diff --git a/ICSharpCode.BamlDecompiler/Handlers/Records/XmlnsPropertyHandler.cs b/ICSharpCode.BamlDecompiler/Handlers/Records/XmlnsPropertyHandler.cs index 7eb462ffb..ac5d8a268 100644 --- a/ICSharpCode.BamlDecompiler/Handlers/Records/XmlnsPropertyHandler.cs +++ b/ICSharpCode.BamlDecompiler/Handlers/Records/XmlnsPropertyHandler.cs @@ -58,12 +58,20 @@ namespace ICSharpCode.BamlDecompiler.Handlers foreach (var asmId in record.AssemblyIds) { var assembly = ctx.Baml.ResolveAssembly(asmId); - ctx.XmlNs.Add(new NamespaceMap(record.Prefix, assembly.FullAssemblyName, record.XmlNamespace)); + // A clr-namespace declaration names its CLR namespace itself. Leaving that unread + // means no lookup by namespace can ever find the declaration the document made, + // and every type in it gets a second prefix of its own (issue #2253). + XamlUtils.TryParseClrNamespace(record.XmlNamespace, out string declaredClrNamespace); + ctx.XmlNs.Add(new NamespaceMap(record.Prefix, assembly.FullAssemblyName, record.XmlNamespace, declaredClrNamespace) { + Assembly = assembly.Assembly + }); if (assembly.Assembly?.IsMainModule == true) { foreach (var clrNs in ResolveCLRNamespaces(assembly.Assembly, record.XmlNamespace)) - ctx.XmlNs.Add(new NamespaceMap(record.Prefix, assembly.FullAssemblyName, record.XmlNamespace, clrNs)); + ctx.XmlNs.Add(new NamespaceMap(record.Prefix, assembly.FullAssemblyName, record.XmlNamespace, clrNs) { + Assembly = assembly.Assembly + }); } } diff --git a/ICSharpCode.BamlDecompiler/Xaml/NamespaceMap.cs b/ICSharpCode.BamlDecompiler/Xaml/NamespaceMap.cs index a81bec5cd..33411ccff 100644 --- a/ICSharpCode.BamlDecompiler/Xaml/NamespaceMap.cs +++ b/ICSharpCode.BamlDecompiler/Xaml/NamespaceMap.cs @@ -31,6 +31,13 @@ namespace ICSharpCode.BamlDecompiler.Xaml { public string XmlnsPrefix { get; set; } public string FullAssemblyName { get; set; } + + /// + /// The assembly resolves to, where it could be resolved. + /// The name is the one the document was written against, which is not always the name of + /// the assembly the types actually come from. + /// + public IModule Assembly { get; set; } public string XMLNamespace { get; set; } public string CLRNamespace { get; set; } @@ -47,6 +54,34 @@ namespace ICSharpCode.BamlDecompiler.Xaml CLRNamespace = clrNs; } + /// + /// Whether is the declaration to use for a type named + /// in of + /// . + /// + public static bool Matches(NamespaceMap map, string fullAssemblyName, string clrNs, string typeName) + { + if (map.CLRNamespace != clrNs) + return false; + if (map.FullAssemblyName == fullAssemblyName) + return true; + // The document records the assembly it was written against, while a well-known type + // carries the assembly it resolves to now - "mscorlib" against "System.Private.CoreLib" + // on .NET, say. The two name the same type when the recorded assembly forwards it, and + // then the declaration the document made is the one to use. + return typeName != null && ForwardsOrDeclares(map.Assembly, clrNs, typeName); + } + + static bool ForwardsOrDeclares(IModule assembly, string clrNs, string typeName) + { + if (assembly == null) + return false; + var name = new TopLevelTypeName(clrNs, typeName); + if (assembly.GetTypeDefinition(name) != null) + return true; + return assembly.MetadataFile?.GetTypeForwarder(new FullTypeName(name)).IsNil == false; + } + public override string ToString() => $"{XmlnsPrefix}:[{FullAssemblyName}|{CLRNamespace ?? XMLNamespace}]"; } } \ No newline at end of file diff --git a/ICSharpCode.BamlDecompiler/Xaml/XamlExtension.cs b/ICSharpCode.BamlDecompiler/Xaml/XamlExtension.cs index cd3e664ca..0d45d7a5b 100644 --- a/ICSharpCode.BamlDecompiler/Xaml/XamlExtension.cs +++ b/ICSharpCode.BamlDecompiler/Xaml/XamlExtension.cs @@ -43,7 +43,7 @@ namespace ICSharpCode.BamlDecompiler.Xaml if (value is XamlExtension) sb.Append(((XamlExtension)value).ToString(ctx, ctxElement)); else - sb.Append(value.ToString()); + sb.Append(XamlUtils.QuoteMarkupExtensionValue(value.ToString())); } public string ToString(XamlContext ctx, XElement ctxElement) diff --git a/ICSharpCode.BamlDecompiler/Xaml/XamlType.cs b/ICSharpCode.BamlDecompiler/Xaml/XamlType.cs index 09d287c18..a5c2be0f3 100644 --- a/ICSharpCode.BamlDecompiler/Xaml/XamlType.cs +++ b/ICSharpCode.BamlDecompiler/Xaml/XamlType.cs @@ -64,9 +64,9 @@ namespace ICSharpCode.BamlDecompiler.Xaml string xmlNs = null; if (elem.Annotation() != null) - xmlNs = elem.Annotation().LookupXmlns(FullAssemblyName, TypeNamespace); + xmlNs = elem.Annotation().LookupXmlns(FullAssemblyName, TypeNamespace, TypeName); if (xmlNs == null) - xmlNs = ctx.XmlNs.LookupXmlns(FullAssemblyName, TypeNamespace); + xmlNs = ctx.XmlNs.LookupXmlns(FullAssemblyName, TypeNamespace, TypeName); // Sometimes there's no reference to System.Xaml even if x:Type is used if (xmlNs == null) xmlNs = XamlContext.TryGetXmlNamespace(Assembly, TypeNamespace, elem); diff --git a/ICSharpCode.BamlDecompiler/Xaml/XamlUtils.cs b/ICSharpCode.BamlDecompiler/Xaml/XamlUtils.cs index 572667f1d..34a8b7ce8 100644 --- a/ICSharpCode.BamlDecompiler/Xaml/XamlUtils.cs +++ b/ICSharpCode.BamlDecompiler/Xaml/XamlUtils.cs @@ -20,6 +20,7 @@ THE SOFTWARE. */ +using System; using System.IO; using System.Text; using System.Xml; @@ -29,6 +30,84 @@ namespace ICSharpCode.BamlDecompiler.Xaml { internal static class XamlUtils { + static readonly char[] markupExtensionSpecialCharacters = { ',', '=', '\'', '"', '\\' }; + + /// + /// Quotes an argument of a markup extension if the parser reading the document again would + /// take part of it for grammar: ',' and '=' separate arguments from one another, a quote + /// character starts a quoted value, '\' escapes whatever follows it, and whitespace at + /// either end is dropped. A value carrying none of those is left as it is, because quoting + /// every value would rewrite every document that never needed it. + /// + /// Braces are grammar only where they are unbalanced: a stray '{' opens an extension and a + /// stray '}' closes the surrounding one, while a matched pair inside a value ("{0:C}", + /// "Element[{ns}Name]") is read as text and stays unquoted. A value beginning with '{' is + /// a nested extension that is already written as one, so it is left alone; the "{}" that + /// escapes a leading brace is not, because inside an extension it would open one. + /// + /// + public static string QuoteMarkupExtensionValue(string value) + { + if (value == null) + return null; + if (value.StartsWith("{", StringComparison.Ordinal) && !value.StartsWith("{}", StringComparison.Ordinal)) + { + return value; // a nested markup extension, already written as one + } + if (value.Length > 0 + && !value.StartsWith("{}", StringComparison.Ordinal) + && value.IndexOfAny(markupExtensionSpecialCharacters) < 0 + && BracesAreBalanced(value) + && !char.IsWhiteSpace(value[0]) + && !char.IsWhiteSpace(value[value.Length - 1])) + { + return value; + } + + var quoted = new StringBuilder(value.Length + 2); + quoted.Append('\''); + foreach (char c in value) + { + if (c == '\'' || c == '\\') + quoted.Append('\\'); + quoted.Append(c); + } + quoted.Append('\''); + return quoted.ToString(); + } + + static bool BracesAreBalanced(string value) + { + int depth = 0; + foreach (char c in value) + { + if (c == '{') + depth++; + else if (c == '}' && --depth < 0) + return false; + } + return depth == 0; + } + + /// + /// Reads the CLR namespace out of a "clr-namespace:Some.Namespace;assembly=Some.Assembly" + /// declaration. Such a declaration names its CLR namespace itself; the other form of XML + /// namespace ("http://...") maps to CLR namespaces through XmlnsDefinition attributes + /// instead, and has none of its own. + /// + public static bool TryParseClrNamespace(string xmlNamespace, out string clrNamespace) + { + const string prefix = "clr-namespace:"; + clrNamespace = null; + if (xmlNamespace == null || !xmlNamespace.StartsWith(prefix, StringComparison.Ordinal)) + return false; + clrNamespace = xmlNamespace.Substring(prefix.Length); + int assembly = clrNamespace.IndexOf(';'); + if (assembly >= 0) + clrNamespace = clrNamespace.Substring(0, assembly); + return true; + } + public static string Escape(string value) { if (value.Length == 0) diff --git a/ICSharpCode.BamlDecompiler/XamlContext.cs b/ICSharpCode.BamlDecompiler/XamlContext.cs index 5fc1752aa..2d6e5c104 100644 --- a/ICSharpCode.BamlDecompiler/XamlContext.cs +++ b/ICSharpCode.BamlDecompiler/XamlContext.cs @@ -126,7 +126,7 @@ namespace ICSharpCode.BamlDecompiler } var clrNs = type.Namespace; - var xmlNs = XmlNs.LookupXmlns(fullAssemblyName, clrNs); + var xmlNs = XmlNs.LookupXmlns(fullAssemblyName, clrNs, type.Name); typeMap[id] = xamlType = new XamlType(assembly, fullAssemblyName, clrNs, type.Name, GetXmlNamespace(xmlNs)) { ResolvedType = type diff --git a/ICSharpCode.BamlDecompiler/XmlnsDictionary.cs b/ICSharpCode.BamlDecompiler/XmlnsDictionary.cs index 8f90d47a8..fcb5ccbfe 100644 --- a/ICSharpCode.BamlDecompiler/XmlnsDictionary.cs +++ b/ICSharpCode.BamlDecompiler/XmlnsDictionary.cs @@ -40,11 +40,11 @@ namespace ICSharpCode.BamlDecompiler Element = elem; } - public string LookupXmlns(string fullAssemblyName, string clrNs) + public string LookupXmlns(string fullAssemblyName, string clrNs, string typeName = null) { foreach (var ns in this) { - if (fullAssemblyName == ns.FullAssemblyName && ns.CLRNamespace == clrNs) + if (NamespaceMap.Matches(ns, fullAssemblyName, clrNs, typeName)) return ns.XMLNamespace; } @@ -119,11 +119,11 @@ namespace ICSharpCode.BamlDecompiler return null; } - public string LookupXmlns(string fullAssemblyName, string clrNs) + public string LookupXmlns(string fullAssemblyName, string clrNs, string typeName = null) { foreach (var map in piMappings) { - if (fullAssemblyName == map.Value.FullAssemblyName && map.Value.CLRNamespace == clrNs) + if (NamespaceMap.Matches(map.Value, fullAssemblyName, clrNs, typeName)) return map.Key; } @@ -132,7 +132,7 @@ namespace ICSharpCode.BamlDecompiler { foreach (var ns in scope) { - if (fullAssemblyName == ns.FullAssemblyName && ns.CLRNamespace == clrNs) + if (NamespaceMap.Matches(ns, fullAssemblyName, clrNs, typeName)) return ns.XMLNamespace; } diff --git a/ILSpy.BamlDecompiler.Tests.Windows/Cases/EscapeSequence.xaml b/ILSpy.BamlDecompiler.Tests.Windows/Cases/EscapeSequence.xaml index 004ac0d22..8dd4c16b9 100644 --- a/ILSpy.BamlDecompiler.Tests.Windows/Cases/EscapeSequence.xaml +++ b/ILSpy.BamlDecompiler.Tests.Windows/Cases/EscapeSequence.xaml @@ -7,17 +7,17 @@ - + - + - + diff --git a/ILSpy.BamlDecompiler.Tests.Windows/Cases/MarkupExtension.xaml b/ILSpy.BamlDecompiler.Tests.Windows/Cases/MarkupExtension.xaml index ab059da44..0ba8c34c5 100644 --- a/ILSpy.BamlDecompiler.Tests.Windows/Cases/MarkupExtension.xaml +++ b/ILSpy.BamlDecompiler.Tests.Windows/Cases/MarkupExtension.xaml @@ -1,4 +1,4 @@ -