Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,31 @@ _**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
<PropertyGroup>
<UseUwp>true</UseUwp>
</PropertyGroup>
```

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
<PropertyGroup Condition="'$(TargetFramework)' != '' and $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) != 'windows'">
<ReswPlusAppType>UnoWinUI</ReswPlusAppType>
</PropertyGroup>
```

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

Expand Down
35 changes: 19 additions & 16 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions nuget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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#
Expand Down
2 changes: 2 additions & 0 deletions nuget/ReswPlus.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
<CompilerVisibleProperty Include="DefaultLanguage" />
<CompilerVisibleProperty Include="OutputType" />
<CompilerVisibleProperty Include="UseUwp" />
<CompilerVisibleProperty Include="ReswPlusAppType" />
<CompilerVisibleProperty Include="ReswPlusUseApplicationLanguages" />
<CompilerVisibleProperty Include="ReswPlusGenerateResourceInterfaces" />
<CompilerVisibleProperty Include="ReswPlusTranslationChecks" />
</ItemGroup>
<PropertyGroup>
<AdditionalFileItemNames>$(AdditionalFileItemNames);PRIResource</AdditionalFileItemNames>
<ReswPlusAppType Condition="'$(ReswPlusAppType)' == ''">Auto</ReswPlusAppType>
<!-- Read the plural language from the app runtime language list instead of the .NET UI culture -->
<ReswPlusUseApplicationLanguages Condition="'$(ReswPlusUseApplicationLanguages)' == ''">false</ReswPlusUseApplicationLanguages>
<ReswPlusGenerateResourceInterfaces Condition="'$(ReswPlusGenerateResourceInterfaces)' == ''">true</ReswPlusGenerateResourceInterfaces>
Expand Down
1 change: 1 addition & 0 deletions src/ReswPlus.SourceGenerator/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ private static string DescribeParameter(Localization item, FunctionFormatTagPara
/// </remarks>
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";
}

/// <summary>
Expand All @@ -349,7 +349,7 @@ private IEnumerable<UsingDirectiveSyntax> 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")));
Expand Down
16 changes: 14 additions & 2 deletions src/ReswPlus.SourceGenerator/Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ internal static class Diagnostics
isEnabledByDefault: true);

/// <summary>
/// RESWP0005: the project is neither a UWP nor a WinAppSDK project.
/// RESWP0005: the project is not a UWP, WinAppSDK, or Uno WinUI project.
/// </summary>
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);
Expand Down Expand Up @@ -272,6 +272,17 @@ internal static class Diagnostics
DiagnosticSeverity.Info,
isEnabledByDefault: true);

/// <summary>
/// RESWP0021: the explicit application model selection is not supported.
/// </summary>
public static readonly DiagnosticDescriptor InvalidAppTypeOverride = new(
"RESWP0021",
"Invalid application model override",
"ReswPlusAppType must be Auto, WindowsAppSDK, UWP, or UnoWinUI",
CompatibilityCategory,
DiagnosticSeverity.Error,
isEnabledByDefault: true);

/// <summary>
/// Returns the descriptor of a diagnostic from its identifier.
/// </summary>
Expand All @@ -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."),
};
}
Expand Down
10 changes: 10 additions & 0 deletions src/ReswPlus.SourceGenerator/Pipeline/AppTypeSelection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace ReswPlus.SourceGenerator.Pipeline;

internal enum AppTypeSelection
{
Auto,
WindowsAppSDK,
UWP,
UnoWinUI,
Invalid,
}
40 changes: 39 additions & 1 deletion src/ReswPlus.SourceGenerator/Pipeline/ReswBuildOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private ReswBuildOptions(
string? rootNamespace,
bool useApplicationLanguages,
bool useUwp,
AppTypeSelection appTypeOverride,
bool generateResourceInterfaces)
{
ProjectDir = projectDir;
Expand All @@ -35,6 +36,7 @@ private ReswBuildOptions(
RootNamespace = rootNamespace;
UseApplicationLanguages = useApplicationLanguages;
UseUwp = useUwp;
AppTypeOverride = appTypeOverride;
GenerateResourceInterfaces = generateResourceInterfaces;
}

Expand Down Expand Up @@ -63,10 +65,16 @@ private ReswBuildOptions(
/// A UWP project is otherwise recognized by the <c>Windows.Foundation.UniversalApiContract</c> 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 <c>ReswPlusAppType</c> takes
/// precedence over both the references and this fallback.
/// </remarks>
public bool UseUwp { get; }

/// <summary>
/// Gets the explicit application model selection, or <see cref="AppTypeSelection.Auto"/> to use detection.
/// </summary>
public AppTypeSelection AppTypeOverride { get; }

/// <summary>
/// Gets whether injectable resource interfaces and providers should be generated.
/// </summary>
Expand All @@ -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;
}

/// <summary>
/// Returns the directory the project is rooted at.
/// </summary>
Expand All @@ -119,6 +155,7 @@ public bool Equals(ReswBuildOptions? other)
&& RootNamespace == other.RootNamespace
&& UseApplicationLanguages == other.UseApplicationLanguages
&& UseUwp == other.UseUwp
&& AppTypeOverride == other.AppTypeOverride
&& GenerateResourceInterfaces == other.GenerateResourceInterfaces;
}

Expand All @@ -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();
}
}
27 changes: 18 additions & 9 deletions src/ReswPlus.SourceGenerator/Pipeline/ReswProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down
Loading
Loading