Skip to content

Commit 334803f

Browse files
committed
Make optimizers debuggable through settings and non assembly merging debug build
Added warning to ResourceDictionaryOptimizer when category mapping is not found
1 parent cbfa30f commit 334803f

File tree

12 files changed

+126
-21
lines changed

12 files changed

+126
-21
lines changed

Source/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageVersion Include="StyleCop.Analyzers.Unstable" Version="1.2.0.556" />
1616
<PackageVersion Include="Sundew.Base" Version="15.0.0-u20251116-020129-ci" />
1717
<PackageVersion Include="Sundew.Packaging.Publish" Version="10.0.10-u20251005-144150-ci" />
18-
<PackageVersion Include="Sundew.Xaml.Optimization" Version="3.0.0-u20251116-021731-ci" />
18+
<PackageVersion Include="Sundew.Xaml.Optimization" Version="3.0.0-u20251116-224913-ci" />
1919
<PackageVersion Include="Sundew.Xaml.Theming.Wpf" Version="0.1.0-u20251101-221236-ci" />
2020
<PackageVersion Include="System.IO.Abstractions" Version="22.0.16" />
2121
<PackageVersion Include="xunit" Version="2.9.3" />

Source/Sundew.Xaml.Optimizers.Wpf.Development.Tests/Optimizations/ResourceDictionary/ResourceDictionaryOptimizerTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,55 @@ public async Task Optimize_When_ThereIsOneMergedResourceDictionaryMarkedWithRemo
422422

423423
result.XamlFileChanges.Single().File.Document.ToString().Should().Be(XDocument.Parse(expectedResult).ToString());
424424
}
425+
426+
[Theory]
427+
[InlineData("Application")]
428+
[InlineData("UserControl")]
429+
[InlineData("Page")]
430+
[InlineData("Window")]
431+
public async Task Optimize_When_ThereIsOneMergedResourceDictionaryMarkedWithRemoveCategoryWithSxInDesignNamespace_Then_ResultShouldBeExpectedResult2(string rootType)
432+
{
433+
var input = $@"<{rootType} x:Class=""Sonova.Pegasi.Fsw.App.Wpf.App""
434+
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
435+
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
436+
xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006""
437+
xmlns:sxd=""http://sundew.dev/xaml/design""
438+
Startup=""App_Startup""
439+
mc:Ignorable=""sxd"">
440+
441+
<{rootType}.Resources>
442+
<ResourceDictionary>
443+
<ResourceDictionary.MergedDictionaries>
444+
<!--<ResourceDictionary Source=""pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml"" />-->
445+
<ResourceDictionary Source=""/Sonova.Pegasi.Fsw.App.Wpf.Phonak;component/Themes/Designer.xaml"" sxd:ResourceDictionary.Category=""🎨"" />
446+
</ResourceDictionary.MergedDictionaries>
447+
</ResourceDictionary>
448+
</{rootType}.Resources>
449+
</{rootType}>
450+
";
451+
452+
var expectedResult = $@"<{rootType} x:Class=""Sonova.Pegasi.Fsw.App.Wpf.App""
453+
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
454+
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
455+
xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006""
456+
xmlns:sxd=""http://sundew.dev/xaml/design""
457+
Startup=""App_Startup""
458+
mc:Ignorable=""sxd"">
459+
460+
<{rootType}.Resources>
461+
<ResourceDictionary>
462+
<ResourceDictionary.MergedDictionaries>
463+
<!--<ResourceDictionary Source=""pack://application:,,,/PresentationFramework.Fluent;component/Themes/Fluent.xaml"" />-->
464+
<ResourceDictionary />
465+
</ResourceDictionary.MergedDictionaries>
466+
</ResourceDictionary>
467+
</{rootType}.Resources>
468+
</{rootType}>";
469+
470+
var testee = new ResourceDictionaryOptimizer(new ResourceDictionarySettings([new OptimizationMapping("🎨", OptimizationAction.Remove)], true));
471+
472+
var result = await testee.OptimizeAsync(new XamlFiles([new XamlFile(XDocument.Parse(input), Substitute.For<IFileReference>(), Environment.NewLine), new XamlFile(XDocument.Parse(input), Substitute.For<IFileReference>(), Environment.NewLine)]), this.xamlPlatformInfo, this.projectInfo);
473+
474+
result.XamlFileChanges.Single().File.Document.ToString().Should().Be(XDocument.Parse(expectedResult).ToString());
475+
}
425476
}

Source/Sundew.Xaml.Optimizers.Wpf/Freezing/FreezeResourceSettings.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ namespace Sundew.Xaml.Optimizers.Wpf.Freezing;
1010
using System.Collections.Generic;
1111
using System.ComponentModel;
1212
using Newtonsoft.Json;
13+
using Sundew.Xaml.Optimization;
1314

1415
/// <summary>Settings for <see cref="FreezeResourceOptimizer"/>.</summary>
15-
public class FreezeResourceSettings
16+
public class FreezeResourceSettings : OptimizerSettings
1617
{
1718
/// <summary>Initializes a new instance of the <see cref="FreezeResourceSettings"/> class.</summary>
1819
/// <param name="includeFrameworkTypes">if set to <c>true</c> [include framework types].</param>
1920
/// <param name="includedTypes">The included types.</param>
2021
/// <param name="excludedTypes">The excluded types.</param>
2122
/// <param name="unfreezeMarker">The unfreeze marker.</param>
22-
public FreezeResourceSettings(bool includeFrameworkTypes = true, IReadOnlyList<string>? includedTypes = null, IReadOnlyList<string>? excludedTypes = null, string? unfreezeMarker = null)
23+
/// <param name="debug">A value indicating whether the optimizer should be debugged.</param>
24+
public FreezeResourceSettings(bool includeFrameworkTypes = true, IReadOnlyList<string>? includedTypes = null, IReadOnlyList<string>? excludedTypes = null, string? unfreezeMarker = null, bool debug = false)
25+
: base(debug)
2326
{
2427
this.IncludeFrameworkTypes = includeFrameworkTypes;
2528
this.UnfreezeMarker = unfreezeMarker;

Source/Sundew.Xaml.Optimizers.Wpf/ILRepack.targets

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3-
<Target Name="ILRepacker" BeforeTargets="PostBuildEvent" AfterTargets="Build">
3+
<Target Name="ILRepacker" BeforeTargets="PostBuildEvent" AfterTargets="Build" Condition="$(Configuration.Contains('Release')) AND $(TargetFramework) != ''">
44

55
<ItemGroup>
66
<InputAssemblies Include="$(OutputPath)**\*.dll" />
@@ -12,6 +12,7 @@
1212
</ItemGroup>
1313

1414
<ILRepack
15+
DebugInfo="true"
1516
Parallel="true"
1617
Internalize="true"
1718
InputAssemblies="$(OutputPath)\$(AssemblyName).dll;@(InputAssemblies)"

Source/Sundew.Xaml.Optimizers.Wpf/ResourceDictionary/Internal/OptimizationInfo.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@
77

88
namespace Sundew.Xaml.Optimizers.Wpf.ResourceDictionary.Internal;
99

10+
using Sundew.Xaml.Optimization;
11+
1012
/// <summary>
1113
/// Info about how a resource dictionary should be optimized.
1214
/// </summary>
13-
internal sealed class OptimizationInfo
15+
internal sealed record OptimizationInfo
1416
{
1517
/// <summary>
1618
/// Initializes a new instance of the <see cref="OptimizationInfo"/> class.
1719
/// </summary>
1820
/// <param name="optimizationAction">The optimization action.</param>
1921
/// <param name="replacementType">The replacement type.</param>
2022
/// <param name="source">The binding.</param>
21-
public OptimizationInfo(OptimizationAction optimizationAction, XamlType replacementType, string source)
23+
/// <param name="xamlDiagnostic">The xaml diagnostic.</param>
24+
public OptimizationInfo(OptimizationAction optimizationAction, XamlType replacementType, string source, XamlDiagnostic? xamlDiagnostic = null)
2225
{
2326
this.OptimizationAction = optimizationAction;
2427
this.ReplacementType = replacementType;
2528
this.Source = source;
29+
this.XamlDiagnostic = xamlDiagnostic;
2630
}
2731

2832
/// <summary>
@@ -45,4 +49,9 @@ public OptimizationInfo(OptimizationAction optimizationAction, XamlType replacem
4549
/// The binding.
4650
/// </value>
4751
public string Source { get; }
52+
53+
/// <summary>
54+
/// Gets diagnostic information related to XAML processing, if available.
55+
/// </summary>
56+
public XamlDiagnostic? XamlDiagnostic { get; }
4857
}

Source/Sundew.Xaml.Optimizers.Wpf/ResourceDictionary/Internal/OptimizationProvider.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ namespace Sundew.Xaml.Optimizers.Wpf.ResourceDictionary.Internal;
99

1010
using System.Collections.Generic;
1111
using System.Linq;
12+
using System.Xml;
1213
using System.Xml.Linq;
14+
using Sundew.Base;
15+
using Sundew.Xaml.Optimization;
1316

1417
/// <summary>
1518
/// Provides an optimization info based on an <see cref="XElement"/>.
@@ -66,7 +69,20 @@ public static OptimizationInfo GetOptimizationInfo(
6669
};
6770
}
6871

69-
return new OptimizationInfo(OptimizationAction.None, defaultReplacementType, sourceAttribute.Value);
72+
var (line, column) = categoryAttribute is IXmlLineInfo lineInfo ? (lineInfo.LineNumber, lineInfo.LinePosition) : (-1, -1);
73+
return new OptimizationInfo(
74+
OptimizationAction.None,
75+
defaultReplacementType,
76+
sourceAttribute.Value,
77+
XamlDiagnostic.Warning(
78+
ResourceDictionaryOptimizer.CategoryNotMapped,
79+
"The element: {0} specified a category {1}, but the category was not mapping in settings.",
80+
[resourceDictionaryElement.Name, categoryAttribute.Value],
81+
resourceDictionaryElement.Document?.BaseUri ?? string.Empty,
82+
line,
83+
column,
84+
line,
85+
column));
7086
}
7187

7288
return new OptimizationInfo(OptimizationAction.None, defaultReplacementType, string.Empty);

Source/Sundew.Xaml.Optimizers.Wpf/ResourceDictionary/ResourceDictionaryOptimizer.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace Sundew.Xaml.Optimizers.Wpf.ResourceDictionary;
2424
/// </summary>
2525
public class ResourceDictionaryOptimizer : IXamlOptimizer
2626
{
27+
internal const string CategoryNotMapped = "RDO0001";
2728
private const string Source = "Source";
2829
private static readonly XamlType FallbackReplacementType = new XamlType(Constants.SxPrefix, Constants.SundewXamlOptimizationNamespace, Constants.ResourceDictionaryName);
2930
private readonly ResourceDictionarySettings resourceDictionarySettings;
@@ -50,19 +51,21 @@ public async ValueTask<OptimizationResult> OptimizeAsync(XamlFiles xamlFiles, Xa
5051
var defaultReplaceUncategorized = this.resourceDictionarySettings.ReplaceUncategorized ?? xamlPlatformInfo.XamlPlatform == XamlPlatform.WPF;
5152

5253
var xamlFilesChanges = new ConcurrentBag<XamlFileChange>();
54+
var xamlDiagnostics = new ConcurrentBag<XamlDiagnostic>();
5355
await xamlFiles.ForEachAsync(
5456
(xamlFile, token) =>
5557
{
56-
var mergedResourceDictionaries = xamlFile.Document.XPathSelectElements(
57-
Constants.DefaultResourceDictionaryMergedDictionariesDefaultResourceDictionaryXPath,
58-
xamlPlatformInfo.XmlNamespaceResolver);
59-
var hasBeenOptimized = false;
60-
var hasSxoNamespace = false;
6158
if (!xamlFile.Document.Root.HasValue)
6259
{
6360
return Task.CompletedTask;
6461
}
6562

63+
var mergedResourceDictionaries = xamlFile.Document.XPathSelectElements(
64+
Constants.DefaultResourceDictionaryMergedDictionariesDefaultResourceDictionaryXPath,
65+
xamlPlatformInfo.XmlNamespaceResolver);
66+
67+
var hasBeenOptimized = false;
68+
var hasSxoNamespace = false;
6669
foreach (var xElement in mergedResourceDictionaries.ToList())
6770
{
6871
var optimization = OptimizationProvider.GetOptimizationInfo(
@@ -95,6 +98,11 @@ await xamlFiles.ForEachAsync(
9598
new XAttribute(Constants.SourceText, optimization.Source)));
9699
break;
97100
}
101+
102+
if (optimization.XamlDiagnostic.HasValue)
103+
{
104+
xamlDiagnostics.Add(optimization.XamlDiagnostic);
105+
}
98106
}
99107

100108
if (hasBeenOptimized)
@@ -105,6 +113,6 @@ await xamlFiles.ForEachAsync(
105113
return Task.CompletedTask;
106114
});
107115

108-
return OptimizationResult.From(xamlFilesChanges);
116+
return OptimizationResult.From(xamlFilesChanges, xamlDiagnostics: xamlDiagnostics);
109117
}
110118
}

Source/Sundew.Xaml.Optimizers.Wpf/ResourceDictionary/ResourceDictionarySettings.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,22 @@
88
namespace Sundew.Xaml.Optimizers.Wpf.ResourceDictionary;
99

1010
using System.Collections.Generic;
11+
using Sundew.Xaml.Optimization;
1112

1213
/// <summary>
1314
/// Used to deserialize settings for the <see cref="ResourceDictionaryOptimizer"/>.
1415
/// </summary>
15-
public class ResourceDictionarySettings
16+
public class ResourceDictionarySettings : OptimizerSettings
1617
{
1718
/// <summary>
1819
/// Initializes a new instance of the <see cref="ResourceDictionarySettings"/> class.
1920
/// </summary>
2021
/// <param name="optimizationMappings">The optimization mappings.</param>
21-
/// <param name="replaceUncategorized">The replace uncategorized.</param>
22+
/// <param name="replaceUncategorized">A value indicating whether uncategorized should be replaced.</param>
2223
/// <param name="defaultReplacementType">The default replacement type.</param>
23-
public ResourceDictionarySettings(IReadOnlyList<OptimizationMapping> optimizationMappings, bool? replaceUncategorized = null, string? defaultReplacementType = null)
24+
/// <param name="debug">A value indicating whether the optimizer should be debugged.</param>
25+
public ResourceDictionarySettings(IReadOnlyList<OptimizationMapping> optimizationMappings, bool? replaceUncategorized = null, string? defaultReplacementType = null, bool debug = false)
26+
: base(debug)
2427
{
2528
this.OptimizationMappings = optimizationMappings;
2629
this.ReplaceUncategorized = replaceUncategorized;

Source/Sundew.Xaml.Optimizers.Wpf/StaticToDynamicResource/StaticToDynamicResourceSettings.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@
77

88
namespace Sundew.Xaml.Optimizers.Wpf.StaticToDynamicResource;
99

10+
using Sundew.Xaml.Optimization;
11+
1012
/// <summary>
1113
/// Settings for the <see cref="StaticToDynamicResourceOptimizer"/>.
1214
/// </summary>
13-
public class StaticToDynamicResourceSettings
15+
public class StaticToDynamicResourceSettings : OptimizerSettings
1416
{
1517
/// <summary>
1618
/// Initializes a new instance of the <see cref="StaticToDynamicResourceSettings"/> class with the specified dynamic marker.
1719
/// </summary>
1820
/// <param name="dynamicMarker">The dynamic marker.</param>
19-
public StaticToDynamicResourceSettings(string? dynamicMarker = null)
21+
/// <param name="debug">A value indicating whether the optimizer should be debugged.</param>
22+
public StaticToDynamicResourceSettings(string? dynamicMarker = null, bool debug = false)
23+
: base(debug)
2024
{
2125
this.DynamicMarker = dynamicMarker ?? "🔄️";
2226
}

Source/Sundew.Xaml.Optimizers.Wpf/Sundew.Xaml.Optimizers.Wpf.csproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
<SuppressDependenciesWhenPacking>false</SuppressDependenciesWhenPacking>
2626
<NuspecFile>Sundew.Xaml.Optimizers.Wpf.nuspec</NuspecFile>
2727
<NoWarn>$(NoWarn);NU5128;NU1701</NoWarn>
28+
<SxowPath>$(AssemblyName).dll</SxowPath>
29+
</PropertyGroup>
30+
31+
<PropertyGroup Condition="$(Configuration.Contains('Release')) AND $(TargetFramework) != ''">
32+
<SxowPath>$(AssemblyName).m.dll</SxowPath>
2833
</PropertyGroup>
2934

3035
<ItemGroup>
@@ -56,7 +61,7 @@
5661

5762
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
5863
<ItemGroup>
59-
<OutputFiles Include="$(OutputPath)\$(AssemblyName).m.dll;$(OutputPath)**\System*.dll;$(OutputPath)Sundew.Xaml.Optimization.dll" />
64+
<OutputFiles Include="$(OutputPath)\$(SxowPath);$(OutputPath)**\System*.dll;$(OutputPath)Sundew.Xaml.Optimization.dll" />
6065
<BuildFiles Include="$(MSBuildProjectDirectory)\build\**\*.*" />
6166
<ContentFiles Include="$(MSBuildProjectDirectory)\content\**\*.*" />
6267
</ItemGroup>

0 commit comments

Comments
 (0)