diff --git a/README.md b/README.md
index 24bdb7d..7ac798e 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,9 @@ _**Now available as a Source Generator!**_
⚡ [How to Install ReswPlus](https://github.com/reswplus/ReswPlus/wiki/How-to-install-ReswPlus) – Step-by-step installation guide.
-ReswPlus recognizes a UWP project by the `Windows.Foundation.UniversalApiContract` reference it carries, and a Windows App SDK project by its own. A **UWP project built for Native AOT** has no such reference, so it declares what it is with the standard `UseUwp` property instead, which ReswPlus reads and trusts over the references:
+The source generator supports UWP, Windows App SDK, and Uno WinUI applications and libraries. Uno WinUI is detected automatically from its referenced types and uses `Microsoft.UI.Xaml` helpers with `Windows.ApplicationModel.Resources.ResourceLoader`; no `UseUwp=true` override or `Windows.UI.Xaml` compatibility types are needed.
+
+ReswPlus recognizes a UWP project by the `Windows.Foundation.UniversalApiContract` reference it carries, and a Windows App SDK project by its own. A **UWP project built for Native AOT** has no such reference, so it declares what it is with the standard `UseUwp` property instead. This property fills in the app model only when the references cannot identify it:
```xml
@@ -46,7 +48,21 @@ ReswPlus recognizes a UWP project by the `Windows.Foundation.UniversalApiContrac
```
-Without it, such a project is reported as `RESWP0005` and no code is generated for it. The `samples/UWP` folder has one sample of each kind: `ReswPlusUWPSample` built with .NET Native, and `ReswPlusNativeAotUwpSample` built on modern .NET with Native AOT.
+Without `UseUwp=true` or an explicit `ReswPlusAppType=UWP`, such a project is reported as `RESWP0005` and no code is generated for it. The `samples/UWP` folder has one sample of each kind: `ReswPlusUWPSample` built with .NET Native, and `ReswPlusNativeAotUwpSample` built on modern .NET with Native AOT.
+
+### Override application model detection
+
+Automatic detection remains the default. If it cannot recognize your configuration or selects the wrong app model, opt in to an explicit `ReswPlusAppType`. For example, an Uno application can override its non-Windows heads without changing a Windows App SDK head:
+
+```xml
+
+ UnoWinUI
+
+```
+
+Supported values are `Auto`, `WindowsAppSDK`, `UWP`, and `UnoWinUI`, matched case-insensitively with surrounding whitespace ignored. Unset, empty, or `Auto` keeps automatic detection and the existing `UseUwp` fallback. Any other value reports `RESWP0021` and stops generation rather than silently falling back.
+
+An explicit app model takes precedence over both the detected references and `UseUwp`. The override selects the generated XAML helpers, resource loader, and optional application-language resolver; it does not add platform references. Choose the model whose APIs your target actually provides, and condition the property per target framework when different heads use different app models.
### Injectable resource interfaces
diff --git a/docs/FAQ.md b/docs/FAQ.md
index e21a307..bbd1b77 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -1,32 +1,21 @@
## What's the minimum version of Windows 10 supported for applications using Resw?
-Because ReswPlus's markup extension (deprecated — see below) uses [MarkupExtension](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.markup.markupextension), an app that still uses it needs
-Windows 10 Fall Creators Update (1709). An app that uses only the generated class and `x:Bind` has no such requirement.
+Because ReswPlus's markup extension (deprecated — see below) uses [MarkupExtension](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.markup.markupextension), an app that still uses it needs Windows 10 Fall Creators Update (1709). An app that uses only the generated class and `x:Bind` has no such requirement.
## Can I use the markup extension in an app compiled with Native AOT?
-No, and it is deprecated for that reason. A markup extension is created by the XAML parser while it reads a
-page, and a UWP app compiled with Native AOT cannot create it, so a page that uses one fails to load with
-`Markup extension could not provide value`. Preserving the generated types with `rd.xml` or a trimmer root
-does not change it.
+No, and it is deprecated for that reason. A markup extension is created by the XAML parser while it reads a page, and a UWP app compiled with Native AOT cannot create it, so a page that uses one fails to load with `Markup extension could not provide value`. Preserving the generated types with `rd.xml` or a trimmer root does not change it.
-It is still generated, so an app that does not use Native AOT keeps building, but it is marked `[Obsolete]`
-and the build tells you what to write instead. A page that uses it reports `WMC1500` on the line of the
-markup itself, and the XAML compiler's own generated type table reports `CS0618` alongside it. A build with
-`TreatWarningsAsErrors` will therefore fail until the markup is rewritten or the warning is suppressed.
+It is still generated, so an app that does not use Native AOT keeps building, but it is marked `[Obsolete]` and the build tells you what to write instead. A page that uses it reports `WMC1500` on the line of the markup itself, and the XAML compiler's own generated type table reports `CS0618` alongside it. A build with `TreatWarningsAsErrors` will therefore fail until the markup is rewritten or the warning is suppressed.
-Use `x:Bind`, which reads the same generated members, is resolved while the app is compiled, and works
-whichever way the app is built:
+Use `x:Bind`, which reads the same generated members, is resolved while the app is compiled, and works whichever way the app is built:
| Instead of | Write |
| --- | --- |
| `{strings:Resources Key=Foo}` | `{x:Bind strings:Resources.Foo}` |
| `{strings:Resources Key=Foo, Converter={StaticResource C}}` | `{x:Bind strings:Resources.Foo, Converter={StaticResource C}}` |
-`x:Bind` is not available everywhere the markup extension was. A `Setter` in a `Style`, and a standalone
-`ResourceDictionary` with no code-behind — a shared `Styles.xaml`, say — are the two common cases: `x:Bind`
-needs a compiled-binding host, which those do not have. Set those from code-behind using the same generated
-members:
+`x:Bind` is not available everywhere the markup extension was. A `Setter` in a `Style`, and a standalone `ResourceDictionary` with no code-behind — a shared `Styles.xaml`, say — are the two common cases: `x:Bind` needs a compiled-binding host, which those do not have. Set those from code-behind using the same generated members:
```csharp
myTextBlock.Text = Resources.Foo;
@@ -38,6 +27,20 @@ Everything else ReswPlus generates works with Native AOT.
VB support is currently under development. C++/CX and C++/WinRT support are planned for a future update.
+## Does the source generator support Uno WinUI?
+
+Yes. ReswPlus detects `Microsoft.UI.Xaml.DependencyObject` from the `Uno.UI` assembly or the `Uno.WinUI` assembly used by Uno 7.0 and later, regardless of the NuGet package path or whether the type is a class or interface. It generates `Microsoft.UI.Xaml` helpers and uses `Windows.ApplicationModel.Resources.ResourceLoader.GetForViewIndependentUse`.
+
+Generated macro helpers are still subject to Uno's Windows API support. Uno may report `Uno0001` warnings for unsupported APIs in those helpers, even when the resource uses a different macro.
+
+If you previously worked around `RESWP0005` by setting `UseUwp=true` and adding `Windows.UI.Xaml` versions of `MarkupExtension`, `MarkupExtensionReturnTypeAttribute`, and `IValueConverter`, remove that override and those compatibility types after updating. A separate `WinRT.DynamicWindowsRuntimeCastAttribute` polyfill, if required by your app, is unrelated to ReswPlus and should be kept.
+
+## Can I override the detected application model?
+
+Yes. Set `ReswPlusAppType` to `WindowsAppSDK`, `UWP`, or `UnoWinUI` to explicitly select the app model. Leaving it unset, empty, or `Auto` preserves automatic detection and the existing `UseUwp` fallback. Supported names are case-insensitive and ignore surrounding whitespace; invalid values report `RESWP0021` and stop generation.
+
+An explicit `ReswPlusAppType` takes precedence over both references and `UseUwp`, but does not supply any missing platform APIs. For a multi-targeting Uno app, apply the override only to the affected target frameworks: Windows App SDK heads should keep their own app model. See the [configuration example](../README.md#override-application-model-detection).
+
## Is it free?
Yes and it won't change.
diff --git a/nuget/README.md b/nuget/README.md
index a754e7b..8811bcc 100644
--- a/nuget/README.md
+++ b/nuget/README.md
@@ -10,6 +10,10 @@ dotnet add package ReswPlus
The package adds the generator and its transitive MSBuild integration. Add or update the default-language `.resw` file in your project and ReswPlus generates resource properties and methods during compilation.
+Supports UWP, Windows App SDK, and Uno WinUI applications and libraries. Uno WinUI is detected automatically and uses WinUI XAML helpers with the Windows resource loader, without `UseUwp=true` or `Windows.UI.Xaml` compatibility types.
+
+If automatic detection cannot recognize your configuration or selects the wrong app model, opt in by setting `ReswPlusAppType` to `WindowsAppSDK`, `UWP`, or `UnoWinUI`. The default `Auto` preserves detection and the existing `UseUwp` fallback; an explicit selection overrides both. Configure it per target framework when needed, and retain the corresponding platform references. Invalid values report `RESWP0021`.
+
## Features
- Strongly typed resource properties for XAML and C#
diff --git a/nuget/ReswPlus.targets b/nuget/ReswPlus.targets
index f99e603..c0697d1 100644
--- a/nuget/ReswPlus.targets
+++ b/nuget/ReswPlus.targets
@@ -7,12 +7,14 @@
+
$(AdditionalFileItemNames);PRIResource
+ Auto
false
true
diff --git a/src/ReswPlus.SourceGenerator/AnalyzerReleases.Unshipped.md b/src/ReswPlus.SourceGenerator/AnalyzerReleases.Unshipped.md
index 6108aa0..292ac88 100644
--- a/src/ReswPlus.SourceGenerator/AnalyzerReleases.Unshipped.md
+++ b/src/ReswPlus.SourceGenerator/AnalyzerReleases.Unshipped.md
@@ -14,3 +14,4 @@ RESWP0017 | Resources | Info | A translated resource does not exist in th
RESWP0018 | Resources | Info | A translated value is identical to its default-language value.
RESWP0019 | Resources | Warning | A translation cannot serve the resource shape generated from the default language.
RESWP0020 | Resources | Info | A translation defines variants that do not exist in the default language.
+RESWP0021 | Compatibility | Error | ReswPlusAppType specifies an unsupported application model.
diff --git a/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs b/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs
index 4f8478b..f31bbe7 100644
--- a/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs
+++ b/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs
@@ -336,7 +336,7 @@ private static string DescribeParameter(Localization item, FunctionFormatTagPara
///
private static string XamlNamespace(AppType appType)
{
- return appType == AppType.WindowsAppSDK ? "global::Microsoft.UI.Xaml" : "global::Windows.UI.Xaml";
+ return appType is AppType.WindowsAppSDK or AppType.UnoWinUI ? "global::Microsoft.UI.Xaml" : "global::Windows.UI.Xaml";
}
///
@@ -349,7 +349,7 @@ private IEnumerable GetUsings(AppType appType)
UsingDirective(ParseName("System"))
};
- if (appType == AppType.WindowsAppSDK)
+ if (appType is AppType.WindowsAppSDK or AppType.UnoWinUI)
{
usings.Add(UsingDirective(ParseName("Microsoft.UI.Xaml.Markup")));
usings.Add(UsingDirective(ParseName("Microsoft.UI.Xaml.Data")));
diff --git a/src/ReswPlus.SourceGenerator/Diagnostics.cs b/src/ReswPlus.SourceGenerator/Diagnostics.cs
index 0b9d4f7..06991d8 100644
--- a/src/ReswPlus.SourceGenerator/Diagnostics.cs
+++ b/src/ReswPlus.SourceGenerator/Diagnostics.cs
@@ -71,12 +71,12 @@ internal static class Diagnostics
isEnabledByDefault: true);
///
- /// RESWP0005: the project is neither a UWP nor a WinAppSDK project.
+ /// RESWP0005: the project is not a UWP, WinAppSDK, or Uno WinUI project.
///
public static readonly DiagnosticDescriptor UnrecognizedAppType = new(
"RESWP0005",
"Project type not recognized",
- "ReswPlus only supports UWP and WinAppSDK applications/libraries",
+ "ReswPlus only supports UWP, WinAppSDK, and Uno WinUI applications/libraries; set ReswPlusAppType to WindowsAppSDK, UWP, or UnoWinUI to override automatic detection",
CompatibilityCategory,
DiagnosticSeverity.Error,
isEnabledByDefault: true);
@@ -272,6 +272,17 @@ internal static class Diagnostics
DiagnosticSeverity.Info,
isEnabledByDefault: true);
+ ///
+ /// RESWP0021: the explicit application model selection is not supported.
+ ///
+ public static readonly DiagnosticDescriptor InvalidAppTypeOverride = new(
+ "RESWP0021",
+ "Invalid application model override",
+ "ReswPlusAppType must be Auto, WindowsAppSDK, UWP, or UnoWinUI",
+ CompatibilityCategory,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
///
/// Returns the descriptor of a diagnostic from its identifier.
///
@@ -291,6 +302,7 @@ public static DiagnosticDescriptor GetDescriptor(string id)
"RESWP0003" => MissingRootPath,
"RESWP0004" => UnknownProjectType,
"RESWP0005" => UnrecognizedAppType,
+ "RESWP0021" => InvalidAppTypeOverride,
_ => throw new System.ArgumentOutOfRangeException(nameof(id), id, "No diagnostic carries that identifier."),
};
}
diff --git a/src/ReswPlus.SourceGenerator/Pipeline/AppTypeSelection.cs b/src/ReswPlus.SourceGenerator/Pipeline/AppTypeSelection.cs
new file mode 100644
index 0000000..d45a4dc
--- /dev/null
+++ b/src/ReswPlus.SourceGenerator/Pipeline/AppTypeSelection.cs
@@ -0,0 +1,10 @@
+namespace ReswPlus.SourceGenerator.Pipeline;
+
+internal enum AppTypeSelection
+{
+ Auto,
+ WindowsAppSDK,
+ UWP,
+ UnoWinUI,
+ Invalid,
+}
diff --git a/src/ReswPlus.SourceGenerator/Pipeline/ReswBuildOptions.cs b/src/ReswPlus.SourceGenerator/Pipeline/ReswBuildOptions.cs
index 4f27908..218f8bb 100644
--- a/src/ReswPlus.SourceGenerator/Pipeline/ReswBuildOptions.cs
+++ b/src/ReswPlus.SourceGenerator/Pipeline/ReswBuildOptions.cs
@@ -25,6 +25,7 @@ private ReswBuildOptions(
string? rootNamespace,
bool useApplicationLanguages,
bool useUwp,
+ AppTypeSelection appTypeOverride,
bool generateResourceInterfaces)
{
ProjectDir = projectDir;
@@ -35,6 +36,7 @@ private ReswBuildOptions(
RootNamespace = rootNamespace;
UseApplicationLanguages = useApplicationLanguages;
UseUwp = useUwp;
+ AppTypeOverride = appTypeOverride;
GenerateResourceInterfaces = generateResourceInterfaces;
}
@@ -63,10 +65,16 @@ private ReswBuildOptions(
/// A UWP project is otherwise recognized by the Windows.Foundation.UniversalApiContract reference it
/// carries, which a UWP project built for Native AOT does not have. Such a project says so with this
/// property instead. It fills in what the references don't say rather than overriding them: a compilation
- /// whose references positively identify it is left alone.
+ /// whose references positively identify it is left alone. An explicit ReswPlusAppType takes
+ /// precedence over both the references and this fallback.
///
public bool UseUwp { get; }
+ ///
+ /// Gets the explicit application model selection, or to use detection.
+ ///
+ public AppTypeSelection AppTypeOverride { get; }
+
///
/// Gets whether injectable resource interfaces and providers should be generated.
///
@@ -88,11 +96,39 @@ public static ReswBuildOptions Read(AnalyzerConfigOptions globalOptions)
Get("build_property.RootNamespace"),
bool.TryParse(Get("build_property.ReswPlusUseApplicationLanguages"), out var parsed) && parsed,
bool.TryParse(Get("build_property.UseUwp"), out var parsedUseUwp) && parsedUseUwp,
+ ParseAppTypeOverride(Get("build_property.ReswPlusAppType")),
!bool.TryParse(Get("build_property.ReswPlusGenerateResourceInterfaces"), out var parsedInterfaces) || parsedInterfaces);
string? Get(string key) => globalOptions.TryGetValue(key, out var value) ? value : null;
}
+ private static AppTypeSelection ParseAppTypeOverride(string? value)
+ {
+ value = value?.Trim();
+
+ if (string.IsNullOrEmpty(value) || string.Equals(value, "Auto", StringComparison.OrdinalIgnoreCase))
+ {
+ return AppTypeSelection.Auto;
+ }
+
+ if (string.Equals(value, "WindowsAppSDK", StringComparison.OrdinalIgnoreCase))
+ {
+ return AppTypeSelection.WindowsAppSDK;
+ }
+
+ if (string.Equals(value, "UWP", StringComparison.OrdinalIgnoreCase))
+ {
+ return AppTypeSelection.UWP;
+ }
+
+ if (string.Equals(value, "UnoWinUI", StringComparison.OrdinalIgnoreCase))
+ {
+ return AppTypeSelection.UnoWinUI;
+ }
+
+ return AppTypeSelection.Invalid;
+ }
+
///
/// Returns the directory the project is rooted at.
///
@@ -119,6 +155,7 @@ public bool Equals(ReswBuildOptions? other)
&& RootNamespace == other.RootNamespace
&& UseApplicationLanguages == other.UseApplicationLanguages
&& UseUwp == other.UseUwp
+ && AppTypeOverride == other.AppTypeOverride
&& GenerateResourceInterfaces == other.GenerateResourceInterfaces;
}
@@ -137,6 +174,7 @@ public override int GetHashCode()
hash = (hash * 31) + UseApplicationLanguages.GetHashCode();
hash = (hash * 31) + UseUwp.GetHashCode();
+ hash = (hash * 31) + AppTypeOverride.GetHashCode();
return (hash * 31) + GenerateResourceInterfaces.GetHashCode();
}
}
diff --git a/src/ReswPlus.SourceGenerator/Pipeline/ReswProject.cs b/src/ReswPlus.SourceGenerator/Pipeline/ReswProject.cs
index e4ae65a..3bbff3c 100644
--- a/src/ReswPlus.SourceGenerator/Pipeline/ReswProject.cs
+++ b/src/ReswPlus.SourceGenerator/Pipeline/ReswProject.cs
@@ -108,15 +108,24 @@ public static ReswProject Create(CompilationInfo compilationInfo, ReswBuildOptio
problems.Add(Diagnostics.UnknownProjectType.Id);
}
- // A UWP project built for Native AOT carries no 'Windows.Foundation.UniversalApiContract' reference for
- // the compilation to be recognized by, and says what it is with the 'UseUwp' property instead. The
- // property fills in what the references don't say rather than overriding them, so that a project whose
- // references positively identify it can never be taken for something else by a stray property.
- var appType = compilationInfo.AppType is AppType.Unknown && options.UseUwp
- ? AppType.UWP
- : compilationInfo.AppType;
-
- if (appType is not (AppType.WindowsAppSDK or AppType.UWP))
+ if (options.AppTypeOverride == AppTypeSelection.Invalid)
+ {
+ return Unsupported([.. problems, Diagnostics.InvalidAppTypeOverride.Id]);
+ }
+
+ // The explicit selection overrides detection; UseUwp remains only a fallback for unrecognized projects.
+ var appType = options.AppTypeOverride switch
+ {
+ AppTypeSelection.WindowsAppSDK => AppType.WindowsAppSDK,
+ AppTypeSelection.UWP => AppType.UWP,
+ AppTypeSelection.UnoWinUI => AppType.UnoWinUI,
+ AppTypeSelection.Auto => compilationInfo.AppType is AppType.Unknown && options.UseUwp
+ ? AppType.UWP
+ : compilationInfo.AppType,
+ _ => throw new ArgumentOutOfRangeException(nameof(options.AppTypeOverride)),
+ };
+
+ if (appType is not (AppType.WindowsAppSDK or AppType.UWP or AppType.UnoWinUI))
{
return Unsupported([.. problems, Diagnostics.UnrecognizedAppType.Id]);
}
diff --git a/src/ReswPlus.SourceGenerator/ReswGenerator.cs b/src/ReswPlus.SourceGenerator/ReswGenerator.cs
index 8597bfc..5177d60 100644
--- a/src/ReswPlus.SourceGenerator/ReswGenerator.cs
+++ b/src/ReswPlus.SourceGenerator/ReswGenerator.cs
@@ -26,6 +26,7 @@ public enum AppType
Unknown,
WindowsAppSDK,
UWP,
+ UnoWinUI,
}
///
@@ -264,10 +265,17 @@ private static void EmitSupport(SourceProductionContext spc, ReswSupport support
}
///
- /// Determines the application type (WindowsAppSDK, UWP, or Unknown) by inspecting the compilation's external references.
+ /// Determines the application type by inspecting the compilation's referenced types and assemblies.
///
private static AppType RetrieveAppType(Compilation compilation)
{
+ // Uno provides WinUI XAML types, but implements the Windows resource loader.
+ var dependencyObject = compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.DependencyObject");
+ if (dependencyObject?.ContainingAssembly.Identity.Name is "Uno.UI" or "Uno.WinUI")
+ {
+ return AppType.UnoWinUI;
+ }
+
return compilation.ExternalReferences.Any(r =>
r.Display?.IndexOf("Microsoft.WindowsAppSdk", StringComparison.OrdinalIgnoreCase) >= 0)
? AppType.WindowsAppSDK
diff --git a/tests/ReswPlusUnitTests/AppTypeBuildIntegration.cs b/tests/ReswPlusUnitTests/AppTypeBuildIntegration.cs
new file mode 100644
index 0000000..99e425f
--- /dev/null
+++ b/tests/ReswPlusUnitTests/AppTypeBuildIntegration.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Xunit;
+
+namespace ReswPlusUnitTests;
+
+public class AppTypeBuildIntegration
+{
+ [Theory]
+ [InlineData(null, "Auto")]
+ [InlineData("", "Auto")]
+ [InlineData("WindowsAppSDK", "WindowsAppSDK")]
+ [InlineData("UWP", "UWP")]
+ [InlineData("UnoWinUI", "UnoWinUI")]
+ [InlineData(" uWp ", " uWp ")]
+ [InlineData("Invalid", "Invalid")]
+ public async Task ThePackagedTargetsExposeTheDefaultOrUserSelection(string? selection, string expected)
+ {
+ var properties = new XElement("PropertyGroup");
+ if (selection is not null)
+ {
+ properties.Add(new XElement("ReswPlusAppType", selection));
+ }
+
+ await AssertSelectionAsync(properties, expected);
+ }
+
+ [Theory]
+ [InlineData("", "Auto")]
+ [InlineData("net10.0-android", "UnoWinUI")]
+ [InlineData("net10.0-ios", "UnoWinUI")]
+ [InlineData("net10.0", "UnoWinUI")]
+ [InlineData("net10.0-windows10.0.19041.0", "Auto")]
+ public async Task TheDocumentedConditionLeavesWindowsHeadsOnAutomaticDetection(string targetFramework, string expected)
+ {
+ var readme = File.ReadAllText(Path.Combine(RepositoryPath(), "README.md"));
+ var example = Regex.Match(
+ readme,
+ @"### Override application model detection.*?```xml\s*(?.*?)```",
+ RegexOptions.Singleline);
+ Assert.True(example.Success, "The README must include the per-target-framework override example.");
+
+ var properties = XElement.Parse(example.Groups["xml"].Value);
+ await AssertSelectionAsync(
+ new XElement("PropertyGroup", new XElement("TargetFramework", targetFramework)),
+ expected,
+ properties);
+ }
+
+ private static async Task AssertSelectionAsync(XElement properties, string expected, XElement? additionalProperties = null)
+ {
+ var root = Path.Combine(Path.GetTempPath(), "ReswPlusUnitTests", $"AppType-{Guid.NewGuid():N}");
+ var logDirectory = Path.Combine(AppContext.BaseDirectory, "BuildLogs");
+ var binlog = Path.Combine(logDirectory, $"app-type-{Guid.NewGuid():N}.binlog");
+
+ try
+ {
+ Directory.CreateDirectory(root);
+ Directory.CreateDirectory(logDirectory);
+ var project = new XElement(
+ "Project",
+ properties,
+ additionalProperties,
+ new XElement("Import", new XAttribute(
+ "Project",
+ Path.Combine(RepositoryPath(), "nuget", "ReswPlus.targets"))));
+ var projectPath = Path.Combine(root, "PropertyProbe.proj");
+ project.Save(projectPath);
+
+ var startInfo = new ProcessStartInfo("dotnet")
+ {
+ WorkingDirectory = RepositoryPath(),
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ };
+ foreach (var argument in new[]
+ {
+ "msbuild",
+ projectPath,
+ "-nologo",
+ "-verbosity:quiet",
+ "-getProperty:ReswPlusAppType",
+ "-getItem:CompilerVisibleProperty",
+ $"-bl:{binlog}",
+ })
+ {
+ startInfo.ArgumentList.Add(argument);
+ }
+
+ using var process = Process.Start(startInfo)
+ ?? throw new InvalidOperationException("Could not start dotnet msbuild.");
+ var outputTask = process.StandardOutput.ReadToEndAsync();
+ var errorTask = process.StandardError.ReadToEndAsync();
+ using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(1));
+ try
+ {
+ await process.WaitForExitAsync(timeout.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ process.Kill(entireProcessTree: true);
+ await process.WaitForExitAsync();
+ throw;
+ }
+ var output = await outputTask;
+ var errors = await errorTask;
+ Assert.True(process.ExitCode == 0, $"MSBuild property evaluation failed:{Environment.NewLine}{output}{errors}{Environment.NewLine}Binlog: {binlog}");
+
+ using var result = JsonDocument.Parse(output);
+ Assert.Equal(expected, result.RootElement.GetProperty("Properties").GetProperty("ReswPlusAppType").GetString());
+ var exportedProperties = result.RootElement.GetProperty("Items").GetProperty("CompilerVisibleProperty")
+ .EnumerateArray()
+ .Select(item => item.GetProperty("Identity").GetString());
+ Assert.Contains("ReswPlusAppType", exportedProperties);
+ }
+ finally
+ {
+ if (Directory.Exists(root))
+ {
+ Directory.Delete(root, recursive: true);
+ }
+ }
+ }
+
+ private static string RepositoryPath([CallerFilePath] string sourceFile = "")
+ {
+ return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(sourceFile)!, "..", ".."));
+ }
+}
diff --git a/tests/ReswPlusUnitTests/AppTypeDetection.cs b/tests/ReswPlusUnitTests/AppTypeDetection.cs
new file mode 100644
index 0000000..571ebdd
--- /dev/null
+++ b/tests/ReswPlusUnitTests/AppTypeDetection.cs
@@ -0,0 +1,388 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using ReswPlus.SourceGenerator;
+using ReswPlus.SourceGenerator.Pipeline;
+using Xunit;
+
+namespace ReswPlusUnitTests;
+
+public class AppTypeDetection
+{
+ private static ImmutableArray FrameworkReferences => PlatformStubs.ReferencesFor(AppType.Unknown);
+
+ private static string SampleResources => ReswTestHelpers.CreateResw(
+ ("Welcome", "Welcome!", null),
+ ("Greeting", "Hello {0}", "#Format[String name]"),
+ ("FileCount_None", "No files", null),
+ ("FileCount_One", "{0} file", "#Format[Plural Int count]"),
+ ("FileCount_Other", "{0} files", "#Format[Plural Int count]"));
+
+ [Theory]
+ [InlineData("Uno.UI", "interface", false, false)]
+ [InlineData("Uno.UI", "interface", true, false)]
+ [InlineData("Uno.UI", "class", false, false)]
+ [InlineData("Uno.UI", "class", true, false)]
+ [InlineData("Uno.UI", "interface", false, true)]
+ [InlineData("Uno.UI", "interface", true, true)]
+ [InlineData("Uno.UI", "class", false, true)]
+ [InlineData("Uno.UI", "class", true, true)]
+ [InlineData("Uno.WinUI", "interface", false, false)]
+ [InlineData("Uno.WinUI", "interface", true, false)]
+ [InlineData("Uno.WinUI", "class", false, false)]
+ [InlineData("Uno.WinUI", "class", true, false)]
+ [InlineData("Uno.WinUI", "interface", false, true)]
+ [InlineData("Uno.WinUI", "interface", true, true)]
+ [InlineData("Uno.WinUI", "class", false, true)]
+ [InlineData("Uno.WinUI", "class", true, true)]
+ public void UnoWinUIUsesWinUIHelpersAndWindowsResources(string unoAssemblyName, string typeKind, bool isLibrary, bool useApplicationLanguages)
+ {
+ var reference = CreatePlatformReference(AppType.UnoWinUI, typeKind, unoAssemblyName);
+ Assert.DoesNotContain("Uno", reference.Display!);
+
+ var run = Run([reference], isLibrary, useApplicationLanguages);
+ Assert.Empty(run.Diagnostics);
+
+ var resources = run.Source("Resources.resw");
+ Assert.Contains("using Microsoft.UI.Xaml.Markup;", resources);
+ Assert.Contains("using Microsoft.UI.Xaml.Data;", resources);
+ Assert.Contains(isLibrary ? "\"TestProject/Resources\"" : "\"Resources\"", resources);
+ Assert.Contains("public static string Welcome", resources);
+ Assert.Contains("public static string Greeting(string name)", resources);
+ Assert.Contains("public static string FileCount(int count)", resources);
+ Assert.Contains("[global::Microsoft.UI.Xaml.Markup.MarkupExtensionReturnType(ReturnType = typeof(string))]", resources);
+ Assert.Contains("public global::Microsoft.UI.Xaml.Data.IValueConverter Converter", resources);
+ Assert.Contains("public interface IResources", resources);
+ Assert.Contains("public sealed class ResourcesProvider : IResources", resources);
+
+ var provider = run.Source("ResourceStringProvider");
+ Assert.Contains("using Windows.ApplicationModel.Resources;", provider);
+ Assert.Contains("ResourceLoader.GetForViewIndependentUse(resourceMapId)", provider);
+ var plurals = run.Source("ResourceLoaderExtension");
+ Assert.Contains("GetPlural", plurals);
+ if (useApplicationLanguages)
+ {
+ Assert.Contains("global::Windows.Globalization.ApplicationLanguages.Languages", plurals);
+ }
+ else
+ {
+ Assert.DoesNotContain("global::Windows.Globalization.ApplicationLanguages", plurals);
+ }
+
+ foreach (var source in run.Sources.Values)
+ {
+ Assert.DoesNotContain("Windows.UI.Xaml", source);
+ Assert.DoesNotContain("Microsoft.Windows.ApplicationModel.Resources", source);
+ Assert.DoesNotContain("Microsoft.Windows.Globalization", source);
+ }
+
+ run.AssertCompiles();
+ AssertHelperTypes(run.OutputCompilation, "Microsoft.UI.Xaml", unoAssemblyName);
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, "Microsoft.UI.Xaml", "Microsoft.Windows.ApplicationModel.Resources")]
+ [InlineData(AppType.UWP, "Windows.UI.Xaml", "Windows.ApplicationModel.Resources")]
+ public void ExistingPlatformsKeepTheirHelpersAndResourceLoaders(AppType appType, string xamlNamespace, string resourceNamespace)
+ {
+ var run = Run([CreatePlatformReference(appType)]);
+
+ Assert.Empty(run.Diagnostics);
+ Assert.Equal(ReswTestHelpers.GenerateCode(SampleResources, appType), run.Source("Resources.resw"));
+ var provider = run.Source("ResourceStringProvider");
+ Assert.Contains($"using {resourceNamespace};", provider);
+ Assert.Contains(
+ appType == AppType.UWP
+ ? "ResourceLoader.GetForViewIndependentUse(resourceMapId)"
+ : "new ResourceLoader(ResourceLoader.GetDefaultResourceFilePath(), resourceMapId)",
+ provider);
+ run.AssertCompiles();
+ AssertHelperTypes(run.OutputCompilation, xamlNamespace, appType == AppType.UWP ? "Windows.Foundation.UniversalApiContract" : "Microsoft.WinUI");
+ }
+
+ [Theory]
+ [InlineData("Unrelated.UI", "Microsoft.UI.Xaml")]
+ [InlineData("Uno.WinUI.Unrelated", "Microsoft.UI.Xaml")]
+ [InlineData("Uno.UI", "Windows.UI.Xaml")]
+ [InlineData("Uno.WinUI", "Windows.UI.Xaml")]
+ public void MatchingNamespaceOrSimilarAssemblyNameAloneDoesNotSelectUno(string assemblyName, string xamlNamespace)
+ {
+ var reference = CreateReference(assemblyName, $"namespace {xamlNamespace} {{ public interface DependencyObject {{ }} }}");
+
+ AssertUnrecognized(Run([reference]));
+ }
+
+ [Theory]
+ [InlineData("Uno.UI")]
+ [InlineData("Uno.WinUI")]
+ public void MissingDependencyObjectDoesNotSelectUno(string unoAssemblyName)
+ {
+ AssertUnrecognized(Run([CreateReference(unoAssemblyName, "public class UnrelatedType { }")]));
+ }
+
+ [Theory]
+ [InlineData("Uno.UI")]
+ [InlineData("Uno.WinUI")]
+ public void AmbiguousDependencyObjectDoesNotSelectUno(string unoAssemblyName)
+ {
+ const string source = "namespace Microsoft.UI.Xaml { public interface DependencyObject { } }";
+ var run = Run([CreateReference(unoAssemblyName, source), CreateReference("Unrelated.UI", source)]);
+
+ Assert.Null(run.OutputCompilation.GetTypeByMetadataName("Microsoft.UI.Xaml.DependencyObject"));
+ AssertUnrecognized(run);
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, "Uno.UI")]
+ [InlineData(AppType.UWP, "Uno.UI")]
+ [InlineData(AppType.WindowsAppSDK, "Uno.WinUI")]
+ [InlineData(AppType.UWP, "Uno.WinUI")]
+ public void UnoWinUIDetectionTakesPrecedenceOverReferencePathHints(AppType hintedAppType, string unoAssemblyName)
+ {
+ var hintedReference = CreateReference("PathHint", "public class UnrelatedType { }", GetReferencePath(hintedAppType));
+ var run = Run([CreatePlatformReference(AppType.UnoWinUI, unoAssemblyName: unoAssemblyName), hintedReference]);
+
+ Assert.Empty(run.Diagnostics);
+ Assert.Contains("using Microsoft.UI.Xaml.Markup;", run.Source("Resources.resw"));
+ Assert.Contains("using Windows.ApplicationModel.Resources;", run.Source("ResourceStringProvider"));
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, false)]
+ [InlineData(AppType.WindowsAppSDK, true)]
+ [InlineData(AppType.UWP, false)]
+ [InlineData(AppType.UWP, true)]
+ public void RejectedUnoTypesPreserveExistingFallbacks(AppType appType, bool ambiguous)
+ {
+ var references = ImmutableArray.Create(CreatePlatformReference(appType));
+ if (ambiguous)
+ {
+ const string source = "namespace Microsoft.UI.Xaml { public interface DependencyObject { } }";
+ references = references.Add(CreateReference("Uno.UI", source)).Add(CreateReference("Unrelated.UI", source));
+ }
+ else
+ {
+ references = references.Add(CreateReference("Uno.UI", "public class UnrelatedType { }"));
+ }
+
+ var run = Run(references);
+
+ Assert.Empty(run.Diagnostics);
+ Assert.Equal(ReswTestHelpers.GenerateCode(SampleResources, appType), run.Source("Resources.resw"));
+ Assert.Contains(
+ appType == AppType.UWP ? "using Windows.ApplicationModel.Resources;" : "using Microsoft.Windows.ApplicationModel.Resources;",
+ run.Source("ResourceStringProvider"));
+ }
+
+ [Fact]
+ public void WinAppSDKStillTakesPrecedenceOverUWPReferencePath()
+ {
+ var run = Run([
+ CreatePlatformReference(AppType.WindowsAppSDK),
+ CreateReference("UwpPathHint", "public class UnrelatedType { }", GetReferencePath(AppType.UWP))]);
+
+ Assert.Empty(run.Diagnostics);
+ Assert.Equal(ReswTestHelpers.GenerateCode(SampleResources), run.Source("Resources.resw"));
+ Assert.Contains("using Microsoft.Windows.ApplicationModel.Resources;", run.Source("ResourceStringProvider"));
+ }
+
+ [Theory]
+ [InlineData("Uno.UI")]
+ [InlineData("Uno.WinUI")]
+ public void ChangingReferencesInvalidatesTheIncrementalAppType(string unoAssemblyName)
+ {
+ var before = Run([CreatePlatformReference(AppType.WindowsAppSDK)]);
+ Assert.Contains("using Microsoft.Windows.ApplicationModel.Resources;", before.Source("ResourceStringProvider"));
+
+ var after = before.RunAgainWithReferences(FrameworkReferences.Add(CreatePlatformReference(AppType.UnoWinUI, unoAssemblyName: unoAssemblyName)));
+
+ Assert.Empty(after.Diagnostics);
+ Assert.Contains("using Windows.ApplicationModel.Resources;", after.Source("ResourceStringProvider"));
+ Assert.Equal(before.Source("Resources.resw"), after.Source("Resources.resw"));
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.CompilationInfo));
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Project));
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Support));
+ after.AssertCompiles();
+
+ Assert.NotEqual(
+ new CompilationInfo(true, AppType.WindowsAppSDK, "TestProject"),
+ new CompilationInfo(true, AppType.UnoWinUI, "TestProject"));
+ Assert.Equal(
+ new CompilationInfo(true, AppType.UnoWinUI, "TestProject"),
+ new CompilationInfo(true, AppType.UnoWinUI, "TestProject"));
+ }
+
+ [Fact]
+ public void MovingDependencyObjectToTheUnoWinUIAssemblyKeepsTheAppModelUnchanged()
+ {
+ var before = Run([CreatePlatformReference(AppType.UnoWinUI)]);
+ var after = before.RunAgainWithReferences(FrameworkReferences.Add(
+ CreatePlatformReference(AppType.UnoWinUI, unoAssemblyName: "Uno.WinUI")));
+
+ Assert.Empty(after.Diagnostics);
+ Assert.Equal(before.Source("Resources.resw"), after.Source("Resources.resw"));
+ Assert.Equal(before.Source("ResourceStringProvider"), after.Source("ResourceStringProvider"));
+ after.AssertReused(TrackingNames.CompilationInfo);
+ after.AssertReused(TrackingNames.Project);
+ after.AssertReused(TrackingNames.Generation);
+ after.AssertReused(TrackingNames.Support);
+ AssertHelperTypes(after.OutputCompilation, "Microsoft.UI.Xaml", "Uno.WinUI");
+ after.AssertCompiles();
+ }
+
+ [Fact]
+ public void ADependencyObjectDefinedInBothUnoAssembliesIsAmbiguous()
+ {
+ const string source = "namespace Microsoft.UI.Xaml { public interface DependencyObject { } }";
+ var run = Run([CreateReference("Uno.UI", source), CreateReference("Uno.WinUI", source)]);
+
+ Assert.Null(run.OutputCompilation.GetTypeByMetadataName("Microsoft.UI.Xaml.DependencyObject"));
+ AssertUnrecognized(run);
+ }
+
+ private static void AssertHelperTypes(Compilation compilation, string xamlNamespace, string assemblyName)
+ {
+ var extension = compilation.GetTypeByMetadataName("TestProject.Strings.ResourcesExtension");
+ Assert.NotNull(extension);
+ Assert.Equal($"{xamlNamespace}.Markup.MarkupExtension", extension.BaseType!.ToDisplayString());
+ Assert.Equal(assemblyName, extension.BaseType.ContainingAssembly.Identity.Name);
+ var converter = Assert.IsAssignableFrom(Assert.Single(extension.GetMembers("Converter")));
+ Assert.Equal($"{xamlNamespace}.Data.IValueConverter", converter.Type.ToDisplayString());
+ Assert.Equal(assemblyName, converter.Type.ContainingAssembly.Identity.Name);
+ Assert.Contains(extension.GetAttributes(), attribute =>
+ attribute.AttributeClass?.ToDisplayString() == $"{xamlNamespace}.Markup.MarkupExtensionReturnTypeAttribute" &&
+ attribute.AttributeClass.ContainingAssembly.Identity.Name == assemblyName);
+ }
+
+ [Theory]
+ [InlineData(false, "Uno.UI")]
+ [InlineData(true, "Uno.UI")]
+ [InlineData(false, "Uno.WinUI")]
+ [InlineData(true, "Uno.WinUI")]
+ public void UseUwpDoesNotOverrideUnoWinUIReferences(bool useUwp, string unoAssemblyName)
+ {
+ var run = Run([CreatePlatformReference(AppType.UnoWinUI, unoAssemblyName: unoAssemblyName)], useUwp: useUwp);
+
+ Assert.Empty(run.Diagnostics);
+ AssertHelperTypes(run.OutputCompilation, "Microsoft.UI.Xaml", unoAssemblyName);
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(false, "Uno.UI")]
+ [InlineData(true, "Uno.UI")]
+ [InlineData(false, "Uno.WinUI")]
+ [InlineData(true, "Uno.WinUI")]
+ public void UnoWinUIHonorsResourceInterfaceGeneration(bool generateResourceInterfaces, string unoAssemblyName)
+ {
+ var run = Run(
+ [CreatePlatformReference(AppType.UnoWinUI, unoAssemblyName: unoAssemblyName)],
+ generateResourceInterfaces: generateResourceInterfaces);
+ var resources = run.Source("Resources.resw");
+
+ Assert.Empty(run.Diagnostics);
+ Assert.Equal(generateResourceInterfaces, resources.Contains("public interface IResources"));
+ Assert.Equal(generateResourceInterfaces, resources.Contains("public sealed class ResourcesProvider : IResources"));
+ run.AssertCompiles();
+ }
+
+ private static void AssertUnrecognized(ReswGeneratorRun run)
+ {
+ Assert.Equal("RESWP0005", Assert.Single(run.Diagnostics).Id);
+ Assert.Empty(run.Sources);
+ }
+
+ private static ReswGeneratorRun Run(
+ ImmutableArray references,
+ bool isLibrary = false,
+ bool useApplicationLanguages = false,
+ bool? useUwp = null,
+ bool? generateResourceInterfaces = null)
+ {
+ return ReswGeneratorHarness.Run(
+ [ReswGeneratorHarness.File("en-US", SampleResources)],
+ outputType: isLibrary ? "Library" : "WinExe",
+ defaultLanguage: "en-US",
+ useApplicationLanguages: useApplicationLanguages,
+ useUwp: useUwp,
+ generateResourceInterfaces: generateResourceInterfaces,
+ references: FrameworkReferences.AddRange(references),
+ projectDir: null,
+ msBuildProjectFullPath: @"C:\Project\TestProject.csproj");
+ }
+
+ private static MetadataReference CreateReference(string assemblyName, string source, string? filePath = null)
+ {
+ return PlatformStubs.Compile(assemblyName, source, filePath ?? @"C:\References\Platform.dll");
+ }
+
+ private static string GetReferencePath(AppType appType)
+ {
+ return appType switch
+ {
+ AppType.WindowsAppSDK => @"C:\Packages\Microsoft.WindowsAppSdk\Platform.dll",
+ AppType.UWP => @"C:\References\Windows.Foundation.UniversalApiContract.winmd",
+ _ => @"C:\References\Platform.dll"
+ };
+ }
+
+ private static MetadataReference CreatePlatformReference(AppType appType, string typeKind = "class", string unoAssemblyName = "Uno.UI")
+ {
+ var xamlNamespace = appType == AppType.UWP ? "Windows.UI.Xaml" : "Microsoft.UI.Xaml";
+ var resourceNamespace = appType == AppType.WindowsAppSDK
+ ? "Microsoft.Windows.ApplicationModel.Resources"
+ : "Windows.ApplicationModel.Resources";
+ var assemblyName = appType switch
+ {
+ AppType.WindowsAppSDK => "Microsoft.WinUI",
+ AppType.UWP => "Windows.Foundation.UniversalApiContract",
+ _ => unoAssemblyName
+ };
+ var source = $$"""
+ namespace {{xamlNamespace}}
+ {
+ public {{typeKind}} DependencyObject { }
+ }
+ namespace {{xamlNamespace}}.Markup
+ {
+ public abstract class MarkupExtension
+ {
+ protected abstract object ProvideValue();
+ }
+ public sealed class MarkupExtensionReturnTypeAttribute : System.Attribute
+ {
+ public System.Type ReturnType { get; set; }
+ }
+ }
+ namespace {{xamlNamespace}}.Data
+ {
+ public interface IValueConverter
+ {
+ object Convert(object value, System.Type targetType, object parameter, string language);
+ object ConvertBack(object value, System.Type targetType, object parameter, string language);
+ }
+ }
+ namespace {{resourceNamespace}}
+ {
+ public sealed class ResourceLoader
+ {
+ {{(appType == AppType.WindowsAppSDK ? """
+ public ResourceLoader(string filePath, string resourceMapId) { }
+ public static string GetDefaultResourceFilePath() => "";
+ """ : """
+ public static ResourceLoader GetForViewIndependentUse(string resourceMapId) => new ResourceLoader();
+ """)}}
+ public string GetString(string resourceId) => resourceId;
+ }
+ }
+ namespace Windows.Globalization
+ {
+ public static class ApplicationLanguages
+ {
+ public static System.Collections.Generic.IReadOnlyList Languages { get; set; }
+ }
+ }
+ """;
+
+ return CreateReference(assemblyName, source, GetReferencePath(appType));
+ }
+}
diff --git a/tests/ReswPlusUnitTests/AppTypeOverride.cs b/tests/ReswPlusUnitTests/AppTypeOverride.cs
new file mode 100644
index 0000000..926b534
--- /dev/null
+++ b/tests/ReswPlusUnitTests/AppTypeOverride.cs
@@ -0,0 +1,428 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using ReswPlus.SourceGenerator;
+using ReswPlus.SourceGenerator.Pipeline;
+using Xunit;
+
+namespace ReswPlusUnitTests;
+
+public class AppTypeOverride
+{
+ private static string SampleResources => ReswTestHelpers.CreateResw(
+ ("Welcome", "Welcome!", null),
+ ("Greeting", "Hello {0}", "#Format[String name]"),
+ ("Items_One", "{0} item", "#Format[Plural Int count]"),
+ ("Items_Other", "{0} items", "#Format[Plural Int count]"));
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, null)]
+ [InlineData(AppType.WindowsAppSDK, "")]
+ [InlineData(AppType.WindowsAppSDK, " \t ")]
+ [InlineData(AppType.WindowsAppSDK, "Auto")]
+ [InlineData(AppType.WindowsAppSDK, " auto ")]
+ [InlineData(AppType.UWP, null)]
+ [InlineData(AppType.UWP, "")]
+ [InlineData(AppType.UWP, " \t ")]
+ [InlineData(AppType.UWP, "Auto")]
+ [InlineData(AppType.UWP, " auto ")]
+ [InlineData(AppType.UnoWinUI, null)]
+ [InlineData(AppType.UnoWinUI, "")]
+ [InlineData(AppType.UnoWinUI, " \t ")]
+ [InlineData(AppType.UnoWinUI, "Auto")]
+ [InlineData(AppType.UnoWinUI, " auto ")]
+ public void AutomaticSelectionPreservesDetection(AppType appType, string? selection)
+ {
+ var run = Run(appType, selection);
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, appType);
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" \t ")]
+ [InlineData("Auto")]
+ [InlineData(" auto ")]
+ public void AutomaticSelectionPreservesTheLegacyUseUwpFallback(string? selection)
+ {
+ var run = Run(
+ AppType.Unknown,
+ selection,
+ useUwp: true,
+ references: PlatformStubs.NativeAotUwpReferences());
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, AppType.UWP);
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData("Auto")]
+ public void AutomaticSelectionDoesNotInventAnAppModel(string? selection)
+ {
+ var run = Run(AppType.Unknown, selection);
+ var diagnostic = Assert.Single(run.Diagnostics);
+
+ Assert.Equal("RESWP0005", diagnostic.Id);
+ Assert.Contains("ReswPlusAppType", diagnostic.GetMessage());
+ Assert.Empty(run.Sources);
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, false)]
+ [InlineData(AppType.WindowsAppSDK, true)]
+ [InlineData(AppType.UWP, false)]
+ [InlineData(AppType.UWP, true)]
+ [InlineData(AppType.UnoWinUI, false)]
+ [InlineData(AppType.UnoWinUI, true)]
+ public void AnExplicitSelectionRecognizesAnOtherwiseUnknownProjection(AppType appType, bool useApplicationLanguages)
+ {
+ var references = PlatformStubs.ReferencesWithDetectionHint(appType);
+ Assert.Equal("RESWP0005", Assert.Single(Run(AppType.Unknown, references: references).Diagnostics).Id);
+
+ var run = Run(
+ AppType.Unknown,
+ appType.ToString(),
+ useApplicationLanguages: useApplicationLanguages,
+ references: references);
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, appType);
+ Assert.Contains("public interface IResources", run.Source("Resources.resw"));
+ Assert.Contains("public sealed class ResourcesProvider : IResources", run.Source("Resources.resw"));
+ if (useApplicationLanguages)
+ {
+ var pluralSupport = run.Source("ResourceLoaderExtension");
+ Assert.Contains("global::Windows.Globalization.ApplicationLanguages.Languages", pluralSupport);
+ Assert.Equal(
+ appType == AppType.WindowsAppSDK,
+ pluralSupport.Contains("global::Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride"));
+ }
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, AppType.UWP)]
+ [InlineData(AppType.UWP, AppType.WindowsAppSDK)]
+ [InlineData(AppType.UnoWinUI, AppType.WindowsAppSDK)]
+ [InlineData(AppType.UnoWinUI, AppType.UWP)]
+ public void AnExplicitSelectionOverridesMisleadingReferences(AppType actualAppType, AppType hintedAppType)
+ {
+ var references = PlatformStubs.ReferencesWithDetectionHint(actualAppType, hintedAppType);
+ var automatic = Run(AppType.Unknown, references: references);
+ Assert.Empty(automatic.Diagnostics);
+ AssertAppModel(automatic, hintedAppType);
+
+ var explicitSelection = Run(
+ AppType.Unknown,
+ actualAppType.ToString(),
+ useApplicationLanguages: true,
+ references: references);
+
+ Assert.Empty(explicitSelection.Diagnostics);
+ AssertAppModel(explicitSelection, actualAppType);
+ explicitSelection.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK)]
+ [InlineData(AppType.UWP)]
+ [InlineData(AppType.UnoWinUI)]
+ public void AnExplicitSelectionTakesPrecedenceOverUseUwp(AppType appType)
+ {
+ var run = Run(
+ AppType.Unknown,
+ appType.ToString(),
+ useUwp: true,
+ references: PlatformStubs.ReferencesWithDetectionHint(appType));
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, appType);
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK, AppType.UWP)]
+ [InlineData(AppType.WindowsAppSDK, AppType.UnoWinUI)]
+ [InlineData(AppType.UWP, AppType.WindowsAppSDK)]
+ [InlineData(AppType.UWP, AppType.UnoWinUI)]
+ [InlineData(AppType.UnoWinUI, AppType.WindowsAppSDK)]
+ [InlineData(AppType.UnoWinUI, AppType.UWP)]
+ public void AnExplicitSelectionTakesPrecedenceOverEveryDetectedModel(AppType detectedAppType, AppType selectedAppType)
+ {
+ var run = Run(detectedAppType, selectedAppType.ToString(), useUwp: true);
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, selectedAppType);
+ }
+
+ [Theory]
+ [InlineData(AppType.WindowsAppSDK)]
+ [InlineData(AppType.UWP)]
+ [InlineData(AppType.UnoWinUI)]
+ public void OverridesPreserveTheResourceInterfaceGenerationSetting(AppType appType)
+ {
+ var run = ReswGeneratorHarness.Run(
+ [ReswGeneratorHarness.File("en-US", SampleResources)],
+ references: PlatformStubs.ReferencesWithDetectionHint(appType),
+ appTypeOverride: appType.ToString(),
+ generateResourceInterfaces: false);
+
+ Assert.Empty(run.Diagnostics);
+ Assert.DoesNotContain("public interface IResources", run.Source("Resources.resw"));
+ Assert.DoesNotContain("public sealed class ResourcesProvider", run.Source("Resources.resw"));
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData(" windowsappsdk ", AppType.WindowsAppSDK)]
+ [InlineData(" uwp ", AppType.UWP)]
+ [InlineData(" unowinui ", AppType.UnoWinUI)]
+ public void NamedSelectionsIgnoreCaseAndSurroundingWhitespace(string selection, AppType appType)
+ {
+ var run = Run(AppType.Unknown, selection, references: PlatformStubs.ReferencesWithDetectionHint(appType));
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, appType);
+ run.AssertCompiles();
+ }
+
+ [Theory]
+ [InlineData("Unknown")]
+ [InlineData("Uno.WinUI")]
+ [InlineData("WinAppSDK")]
+ [InlineData("3")]
+ [InlineData(" 1 ")]
+ [InlineData("-1")]
+ [InlineData("WindowsAppSDK, UWP")]
+ [InlineData("Auto;UnoWinUI")]
+ [InlineData("UnowinU\u0131")]
+ public void InvalidSelectionsReportAnErrorInsteadOfFallingBack(string selection)
+ {
+ foreach (var detectedAppType in new[] { AppType.Unknown, AppType.WindowsAppSDK, AppType.UWP, AppType.UnoWinUI })
+ {
+ var run = Run(detectedAppType, selection, useUwp: true);
+ var diagnostic = Assert.Single(run.Diagnostics);
+
+ Assert.Equal("RESWP0021", diagnostic.Id);
+ Assert.Equal(DiagnosticSeverity.Error, diagnostic.Severity);
+ Assert.Contains("ReswPlusAppType", diagnostic.GetMessage());
+ Assert.Contains("Auto, WindowsAppSDK, UWP, or UnoWinUI", diagnostic.GetMessage());
+ Assert.Empty(run.Sources);
+ }
+ }
+
+ [Fact]
+ public void AnInvalidSelectionPreservesOtherSetupProblemsWithoutReportingUnrecognizedAppType()
+ {
+ var run = ReswGeneratorHarness.Run(
+ [ReswGeneratorHarness.File("en-US", SampleResources)],
+ outputType: null,
+ appTypeOverride: "Unknown");
+
+ Assert.Equal(new[] { "RESWP0004", "RESWP0021" }, run.DiagnosticIds);
+ Assert.Empty(run.Sources);
+ }
+
+ [Fact]
+ public void AnOverrideDoesNotSupplyMissingPlatformReferences()
+ {
+ var run = Run(AppType.Unknown, "UnoWinUI");
+
+ Assert.Empty(run.Diagnostics);
+ AssertAppModel(run, AppType.UnoWinUI);
+ Assert.Contains(run.OutputCompilation.GetDiagnostics(), diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
+ }
+
+ [Theory]
+ [InlineData(null, "Auto")]
+ [InlineData("", " auto ")]
+ [InlineData(" \t ", "Auto")]
+ [InlineData("WindowsAppSDK", " windowsappsdk ")]
+ [InlineData("UWP", " uwp ")]
+ [InlineData("UnoWinUI", " unowinui ")]
+ [InlineData("Unknown", "AnotherInvalidValue")]
+ public void EquivalentSelectionsCompareAndHashAlike(string? first, string? second)
+ {
+ var firstOptions = ReadOptions(first);
+ var secondOptions = ReadOptions(second);
+
+ Assert.Equal(firstOptions, secondOptions);
+ Assert.Equal(firstOptions.GetHashCode(), secondOptions.GetHashCode());
+ }
+
+ [Theory]
+ [InlineData("auto", "Auto")]
+ [InlineData("windowsappsdk", "WindowsAppSDK")]
+ [InlineData("uwp", "UWP")]
+ [InlineData("unowinui", "UnoWinUI")]
+ [InlineData("Unknown", "Invalid")]
+ public void OptionsCaptureTheParsedSelection(string selection, string expected)
+ {
+ Assert.Equal(expected, ReadOptions(selection).AppTypeOverride.ToString());
+ }
+
+ [Fact]
+ public void AChangedSelectionInvalidatesTheOptionsModel()
+ {
+ Assert.NotEqual(ReadOptions(null), ReadOptions("UnoWinUI"));
+ Assert.NotEqual(ReadOptions("UnoWinUI"), ReadOptions("Unknown"));
+ }
+
+ [Fact]
+ public void SelectionParsingIsIndependentOfTheCurrentCulture()
+ {
+ var previousCulture = CultureInfo.CurrentCulture;
+
+ try
+ {
+ CultureInfo.CurrentCulture = new CultureInfo("tr-TR");
+ Assert.Equal(AppTypeSelection.UnoWinUI, ReadOptions("unowinui").AppTypeOverride);
+ Assert.Equal(AppTypeSelection.WindowsAppSDK, ReadOptions("windowsappsdk").AppTypeOverride);
+ Assert.Equal(AppTypeSelection.Auto, ReadOptions("auto").AppTypeOverride);
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = previousCulture;
+ }
+ }
+
+ [Fact]
+ public void SelectingTheDetectedModelDoesNotRegenerateSources()
+ {
+ var automatic = Run(AppType.WindowsAppSDK);
+ var explicitSelection = automatic.RunAgainWithOptions(Options("WindowsAppSDK"));
+
+ Assert.Empty(explicitSelection.Diagnostics);
+ Assert.Equal(1, explicitSelection.RecomputedCount(TrackingNames.Options));
+ explicitSelection.AssertReused(TrackingNames.CompilationInfo);
+ explicitSelection.AssertReused(TrackingNames.Project);
+ explicitSelection.AssertReused(TrackingNames.Generation);
+ explicitSelection.AssertReused(TrackingNames.Support);
+ AssertSameSources(automatic, explicitSelection);
+ }
+
+ [Fact]
+ public void EquivalentSelectionSpellingsDoNotInvalidateThePipeline()
+ {
+ var before = Run(AppType.UnoWinUI, "UnoWinUI");
+ var after = before.RunAgainWithOptions(Options(" unowinui "));
+
+ after.AssertReused(TrackingNames.Options);
+ after.AssertReused(TrackingNames.Project);
+ after.AssertReused(TrackingNames.Generation);
+ after.AssertReused(TrackingNames.Support);
+ AssertSameSources(before, after);
+ }
+
+ [Fact]
+ public void ChangingOnlyTheSelectionUpdatesEveryAppModelDependentOutput()
+ {
+ var before = Run(AppType.WindowsAppSDK);
+ var after = before.RunAgainWithOptions(Options("UWP"));
+
+ Assert.Empty(after.Diagnostics);
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Options));
+ after.AssertReused(TrackingNames.CompilationInfo);
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Project));
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Generation));
+ Assert.Equal(1, after.RecomputedCount(TrackingNames.Support));
+ AssertAppModel(after, AppType.UWP);
+ Assert.NotEqual(before.Source("Resources.resw"), after.Source("Resources.resw"));
+ Assert.NotEqual(before.Source("ResourceStringProvider"), after.Source("ResourceStringProvider"));
+ }
+
+ [Fact]
+ public void InvalidatingAndRestoringTheSelectionRemovesAndRestoresAllSources()
+ {
+ var before = Run(AppType.UnoWinUI, "UnoWinUI");
+ var invalid = before.RunAgainWithOptions(Options("Unknown"));
+ Assert.Equal("RESWP0021", Assert.Single(invalid.Diagnostics).Id);
+ Assert.Empty(invalid.Sources);
+
+ var restored = invalid.RunAgainWithOptions(Options("UnoWinUI"));
+ Assert.Empty(restored.Diagnostics);
+ AssertSameSources(before, restored);
+ restored.AssertCompiles();
+
+ var automatic = restored.RunAgainWithOptions(Options(null));
+ Assert.Empty(automatic.Diagnostics);
+ AssertSameSources(before, automatic);
+ automatic.AssertReused(TrackingNames.Generation);
+ }
+
+ private static ReswGeneratorRun Run(
+ AppType appType,
+ string? selection = null,
+ bool useUwp = false,
+ bool useApplicationLanguages = false,
+ IEnumerable? references = null)
+ {
+ return ReswGeneratorHarness.Run(
+ [ReswGeneratorHarness.File("en-US", SampleResources)],
+ appType,
+ defaultLanguage: "en-US",
+ useUwp: useUwp,
+ useApplicationLanguages: useApplicationLanguages,
+ references: references,
+ appTypeOverride: selection);
+ }
+
+ private static Dictionary Options(string? selection)
+ {
+ var options = new Dictionary(AnalyzerConfigOptions.KeyComparer)
+ {
+ ["build_property.ProjectDir"] = ReswGeneratorHarness.ProjectDir,
+ ["build_property.OutputType"] = "Library",
+ ["build_property.RootNamespace"] = "TestProject",
+ ["build_property.DefaultLanguage"] = "en-US",
+ };
+ if (selection is not null)
+ {
+ options["build_property.ReswPlusAppType"] = selection;
+ }
+ return options;
+ }
+
+ private static ReswBuildOptions ReadOptions(string? selection)
+ {
+ return ReswBuildOptions.Read(new TestAnalyzerConfigOptionsProvider(Options(selection)).GlobalOptions);
+ }
+
+ private static void AssertSameSources(ReswGeneratorRun first, ReswGeneratorRun second)
+ {
+ Assert.Equal(
+ first.Sources.OrderBy(source => source.Key, StringComparer.Ordinal),
+ second.Sources.OrderBy(source => source.Key, StringComparer.Ordinal));
+ }
+
+ private static void AssertAppModel(ReswGeneratorRun run, AppType appType)
+ {
+ var xamlNamespace = appType == AppType.UWP ? "Windows.UI.Xaml" : "Microsoft.UI.Xaml";
+ var generated = run.Source("Resources.resw");
+ Assert.Contains($"using {xamlNamespace}.Markup;", generated);
+ Assert.Contains($"global::{xamlNamespace}.Markup.MarkupExtensionReturnType", generated);
+ Assert.Contains($"global::{xamlNamespace}.Markup.MarkupExtension", generated);
+ Assert.Contains($"global::{xamlNamespace}.Data.IValueConverter", generated);
+
+ var provider = run.Source("ResourceStringProvider");
+ Assert.Contains(
+ appType == AppType.WindowsAppSDK
+ ? "using Microsoft.Windows.ApplicationModel.Resources;"
+ : "using Windows.ApplicationModel.Resources;",
+ provider);
+ Assert.Contains(
+ appType == AppType.WindowsAppSDK
+ ? "new ResourceLoader(ResourceLoader.GetDefaultResourceFilePath(), resourceMapId)"
+ : "ResourceLoader.GetForViewIndependentUse(resourceMapId)",
+ provider);
+ }
+}
diff --git a/tests/ReswPlusUnitTests/GeneratorHarness.cs b/tests/ReswPlusUnitTests/GeneratorHarness.cs
index f595b1f..f561573 100644
--- a/tests/ReswPlusUnitTests/GeneratorHarness.cs
+++ b/tests/ReswPlusUnitTests/GeneratorHarness.cs
@@ -68,6 +68,8 @@ public static ReswFile File(string language, string content, string baseName = "
/// The name of the assembly being compiled.
/// Whether the compilation should look like a UWP project built for Native AOT, which has the UWP types but no recognizable API contract reference.
/// Files to pass to the compiler on top of , to cover what the generator does with the ones it doesn't own.
+ /// Custom compilation references, or to use the platform stubs.
+ /// The opt-in ReswPlusAppType selection, or to leave it undeclared.
/// The result of the run.
public static ReswGeneratorRun Run(
IEnumerable files,
@@ -83,7 +85,9 @@ public static ReswGeneratorRun Run(
bool? generateResourceInterfaces = null,
string assemblyName = "TestProject",
bool nativeAotUwp = false,
- IEnumerable? additionalFiles = null)
+ IEnumerable? additionalFiles = null,
+ IEnumerable? references = null,
+ string? appTypeOverride = null)
{
var texts = files
.Concat(additionalFiles ?? [])
@@ -102,12 +106,13 @@ public static ReswGeneratorRun Run(
Declare("build_property.RootNamespace", rootNamespace);
Declare("build_property.ReswPlusUseApplicationLanguages", useApplicationLanguages?.ToString().ToLowerInvariant());
Declare("build_property.UseUwp", useUwp?.ToString().ToLowerInvariant());
+ Declare("build_property.ReswPlusAppType", appTypeOverride);
Declare("build_property.ReswPlusGenerateResourceInterfaces", generateResourceInterfaces?.ToString().ToLowerInvariant());
var compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: null,
- nativeAotUwp ? PlatformStubs.NativeAotUwpReferences() : PlatformStubs.ReferencesFor(appType),
+ references ?? (nativeAotUwp ? PlatformStubs.NativeAotUwpReferences() : PlatformStubs.ReferencesFor(appType)),
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var driver = CSharpGeneratorDriver.Create(
@@ -243,6 +248,29 @@ public ReswGeneratorRun RunAgain(IEnumerable files)
return From(_driver.ReplaceAdditionalTexts(texts), _inputCompilation, texts);
}
+ ///
+ /// Runs the generator again with new compilation references, reusing the state of the previous run.
+ ///
+ /// The references of the new compilation.
+ /// The result of the run with the new references.
+ public ReswGeneratorRun RunAgainWithReferences(IEnumerable references)
+ {
+ return From(_driver, _inputCompilation.RemoveAllReferences().AddReferences(references), _texts);
+ }
+
+ ///
+ /// Runs the generator again with new project options, reusing its compilation, files, and cached state.
+ ///
+ /// The complete set of global project options for the new run.
+ /// The result of the run with the new options.
+ public ReswGeneratorRun RunAgainWithOptions(IReadOnlyDictionary globalOptions)
+ {
+ return From(
+ _driver.WithUpdatedAnalyzerConfigOptions(new TestAnalyzerConfigOptionsProvider(globalOptions)),
+ _inputCompilation,
+ _texts);
+ }
+
///
/// Returns the source emitted under the hint name holding the given text.
///
@@ -550,10 +578,19 @@ public static class ApplicationLanguages
.Select(path => (MetadataReference)MetadataReference.CreateFromFile(path))]);
private static readonly Lazy WindowsAppSdk = new(() =>
- Compile("ReswPlusTests.WindowsAppSdkStub", WindowsAppSdkStub, @"C:\Packages\Microsoft.WindowsAppSdk\Microsoft.WindowsAppSdk.dll"));
+ Compile("ReswPlusTests.WindowsAppSdkStub", WindowsAppSdkStub, @"C:\Packages\Microsoft.WindowsAppSdk\Microsoft.WindowsAppSdk.dll", registerForRuntime: true));
private static readonly Lazy Uwp = new(() =>
- Compile("ReswPlusTests.UwpStub", UwpStub, @"C:\Packages\UAP\Windows.Foundation.UniversalApiContract.winmd"));
+ Compile("ReswPlusTests.UwpStub", UwpStub, @"C:\Packages\UAP\Windows.Foundation.UniversalApiContract.winmd", registerForRuntime: true));
+
+ private static readonly string UnoWinUIStub = UwpStub.Replace("Windows.UI.Xaml", "Microsoft.UI.Xaml");
+
+ private static readonly Lazy UnoWinUI = new(() =>
+ Compile(
+ "Uno.WinUI",
+ UnoWinUIStub + "\nnamespace Microsoft.UI.Xaml { public interface DependencyObject { } }",
+ @"C:\References\Platform.dll",
+ registerForRuntime: true));
///
/// The same UWP types, under a name the app type detection cannot recognize.
@@ -564,7 +601,7 @@ public static class ApplicationLanguages
/// such a project look like no supported app type at all unless it declares UseUwp.
///
private static readonly Lazy NativeAotUwp = new(() =>
- Compile("ReswPlusTests.NativeAotUwpStub", UwpStub, @"C:\Packages\Microsoft.Windows.SDK.NET\Microsoft.Windows.SDK.NET.dll"));
+ Compile("ReswPlusTests.NativeAotUwpStub", UwpStub, @"C:\Packages\Microsoft.Windows.SDK.NET\Microsoft.Windows.SDK.NET.dll", registerForRuntime: true));
///
/// Returns the references of a UWP project built for Native AOT: the UWP types are there, but nothing names
@@ -586,10 +623,41 @@ public static ImmutableArray ReferencesFor(AppType appType)
{
AppType.WindowsAppSDK => RuntimeReferences.Value.Add(WindowsAppSdk.Value),
AppType.UWP => RuntimeReferences.Value.Add(Uwp.Value),
+ AppType.UnoWinUI => RuntimeReferences.Value.Add(UnoWinUI.Value),
_ => RuntimeReferences.Value,
};
}
+ ///
+ /// Returns the APIs of an app model under an unrecognized or misleading reference path.
+ ///
+ /// The app model whose APIs are available.
+ /// The app model suggested by the path, or for no hint.
+ /// The references with no conclusive Uno type identity.
+ public static ImmutableArray ReferencesWithDetectionHint(AppType appType, AppType? hintedAppType = null)
+ {
+ var source = appType switch
+ {
+ AppType.WindowsAppSDK => WindowsAppSdkStub,
+ AppType.UWP => UwpStub,
+ AppType.UnoWinUI => UnoWinUIStub,
+ _ => throw new ArgumentOutOfRangeException(nameof(appType)),
+ };
+ var path = hintedAppType switch
+ {
+ AppType.WindowsAppSDK => @"C:\Packages\Microsoft.WindowsAppSdk\Platform.dll",
+ AppType.UWP => @"C:\Packages\UAP\Windows.Foundation.UniversalApiContract.winmd",
+ null => @"C:\References\Platform.dll",
+ _ => throw new ArgumentOutOfRangeException(nameof(hintedAppType)),
+ };
+
+ return RuntimeReferences.Value.Add(Compile(
+ $"ReswPlusTests.Custom{appType}",
+ source,
+ path,
+ registerForRuntime: true));
+ }
+
///
/// The images of the stubs compiled so far, so that generated code referencing one can be run and not only
/// compiled.
@@ -627,7 +695,15 @@ private static bool IsWindowsProjection(string path)
|| name.Equals("WinRT.Runtime", StringComparison.OrdinalIgnoreCase);
}
- private static MetadataReference Compile(string assemblyName, string source, string filePath)
+ ///
+ /// Compiles platform types into an in-memory reference with a caller-supplied identity and display path.
+ ///
+ /// The defining assembly identity.
+ /// The source of the platform types.
+ /// The reference path the generator sees.
+ /// Whether the reference is a shared platform stub that can be loaded at runtime.
+ /// The compiled reference.
+ public static MetadataReference Compile(string assemblyName, string source, string filePath, bool registerForRuntime = false)
{
var compilation = CSharpCompilation.Create(
assemblyName,
@@ -650,7 +726,10 @@ private static MetadataReference Compile(string assemblyName, string source, str
// path the real framework would have.
var image = peStream.ToArray();
- StubImages[assemblyName] = image;
+ if (registerForRuntime)
+ {
+ StubImages[assemblyName] = image;
+ }
return AssemblyMetadata.CreateFromImage(image)
.GetReference(filePath: filePath, display: filePath);