From b2e1836b88b051a2727b5663873a196f94035c27 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Sun, 2 Feb 2025 03:32:39 -0800 Subject: [PATCH 1/3] init --- .github/workflows/dotnet.yml | 2 +- .../ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 28928f3..e9f595c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -44,7 +44,7 @@ jobs: run: nuget restore ReswPlus.sln - name: Build Solution - run: msbuild ReswPlus.sln /p:Configuration=${{ env.BUILD_CONFIG }} /p:Platform="Any CPU" + run: msbuild ReswPlus.sln /p:Configuration=${{ env.BUILD_CONFIG }} /p:Platform="Any CPU" /p:RuntimeIdentifierOverride=win-x64 - name: Run Unit Tests run: dotnet test tests/ReswPlusUnitTests/ReswPlusUnitTests.csproj --configuration ${{ env.BUILD_CONFIG }} --no-build --verbosity normal diff --git a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj index c0a6c7b..34b0de1 100644 --- a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj +++ b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj @@ -7,8 +7,7 @@ ReswPlusWinAppSDKSample app.manifest x86;x64;ARM64 - win-x86;win-x64;win-arm64 - win10-x86;win10-x64;win10-arm64 + win-x86;win-x64;win-arm64 win-$(Platform).pubxml true true @@ -18,7 +17,9 @@ True 10.0.19041.0 - + + $(RuntimeIdentifierOverride) + From d2e3bcc58df9867afc656e3a32c3321e9bed656c Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Sun, 2 Feb 2025 17:51:36 -0800 Subject: [PATCH 2/3] update samples --- samples/UWP/ReswPlusUWPSample/ReswPlusUWPSample.csproj | 10 +++++++++- .../ReswPlusWinAppSDKSample.csproj | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/samples/UWP/ReswPlusUWPSample/ReswPlusUWPSample.csproj b/samples/UWP/ReswPlusUWPSample/ReswPlusUWPSample.csproj index 6d8d261..ae7c3d5 100644 --- a/samples/UWP/ReswPlusUWPSample/ReswPlusUWPSample.csproj +++ b/samples/UWP/ReswPlusUWPSample/ReswPlusUWPSample.csproj @@ -18,10 +18,18 @@ 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true - 5E71F95FCAD47B85E52CC33EF85A3981DD6BE171 + + ReswPlusUWPSample_TemporaryKey.pfx True true + False + SHA256 + True + True + Always + x86|x64 + 0 true diff --git a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj index 34b0de1..dfd1caa 100644 --- a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj +++ b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj @@ -12,10 +12,17 @@ true true Icon.png - D1C162AC5FB617F237A6E3B015863C777293065E ReswPlusWinAppSDKSample_TemporaryKey.pfx True 10.0.19041.0 + False + SHA256 + True + False + True + Always + x86|x64|arm64 + 0 $(RuntimeIdentifierOverride) From c7b9f77895f5c6c714c0999d5474150f1e8e58de Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Sun, 2 Feb 2025 19:45:05 -0800 Subject: [PATCH 3/3] Enable nullable reference types and optimize performance for the release build. --- nuget/README.md | 11 +- ...eswPlus.SourceGenerator.NugetPackage.props | 6 +- .../ReswPlusWinAppSDKSample.csproj | 5 +- .../Converters/AndroidXMLConverter.cs | 31 +- .../Parameters/AndroidToReswParameters.cs | 4 +- .../Parameters/ReswToAndroidParameters.cs | 4 +- src/ReswPlus.CommandLine/Program.cs | 6 +- src/ReswPlus.CommandLine/ReswPlusCmd.csproj | 1 + .../Interfaces/IErrorLogger.cs | 4 +- .../ResourceParser/FormatTag.cs | 62 +-- .../ResourceParser/ReswFilters.cs | 29 +- .../ResourceParser/ReswInfo.cs | 7 +- .../ResourceParser/ReswItem.cs | 4 +- .../ResourceParser/ReswParser.cs | 9 +- .../ClassGenerators/GenerationResult.cs | 5 + .../ClassGenerators/Models/Localization.cs | 40 +- .../Models/StronglyTypedClass.cs | 22 +- .../ClassGenerators/PluralFormsRetriever.cs | 376 +++++++++--------- .../ClassGenerators/ReswClassGenerator.cs | 65 ++- .../CodeGenerators/CsharpCodeGenerator.cs | 24 +- .../CodeGenerators/ICodeGenerator.cs | 12 +- src/ReswPlus.SourceGenerator/ReswGenerator.cs | 348 ++++++++-------- .../ReswPlus.SourceGenerator.csproj | 31 +- .../Templates/Macros/Macros.txt | 44 +- .../Plurals/ResourceLoaderExtension.txt | 2 +- .../ReswPlusUnitTests.csproj | 8 +- 26 files changed, 635 insertions(+), 525 deletions(-) diff --git a/nuget/README.md b/nuget/README.md index e342e4c..3c04f3b 100644 --- a/nuget/README.md +++ b/nuget/README.md @@ -1 +1,10 @@ -Reswplus \ No newline at end of file +**ReswPlus** is a C# Source Generator for Visual Studio that enhances `.resw` files with a powerful set of features, making localization easier, safer, and more efficient. + +## Features + +- **Strongly typed static properties** for safer and more efficient string access. +- **Automatic generation of string formatting methods**, supporting: + - Typed and named parameters, literal strings, string references, and macros. +- **Pluralization support** for *196 languages*, including handling empty states when the item count is zero. +- **Variant support** for managing multiple versions of a string. +- **Generation of a markup extension** for accessing strings with **compile-time verification**. \ No newline at end of file diff --git a/nuget/ReswPlus.SourceGenerator.NugetPackage.props b/nuget/ReswPlus.SourceGenerator.NugetPackage.props index cc30652..5e1e7de 100644 --- a/nuget/ReswPlus.SourceGenerator.NugetPackage.props +++ b/nuget/ReswPlus.SourceGenerator.NugetPackage.props @@ -1,13 +1,13 @@ - 0.3.0.1 + 0.3.1.1 true - ReswPlus + ReswPlus - Source Generator ReswPlus $(FileVersion) Rudy Huyn - ReswPlus - Advanced File Code Generator for Resw files + ReswPlus - Source Generator for Resw files https://github.com/reswplus/ReswPlus/ Icon.png Copyright 2025 diff --git a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj index dfd1caa..e40f4b6 100644 --- a/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj +++ b/samples/WinAppSDK/ReswPlusWinAppSDKSample/ReswPlusWinAppSDKSample.csproj @@ -50,7 +50,10 @@ - + + {44d125b3-6c8a-4f2f-a76f-bffb2ad7e70d} + ReswPlus.SourceGenerator + diff --git a/src/ReswPlus.CommandLine/Converters/AndroidXMLConverter.cs b/src/ReswPlus.CommandLine/Converters/AndroidXMLConverter.cs index 629f21f..2ba338f 100644 --- a/src/ReswPlus.CommandLine/Converters/AndroidXMLConverter.cs +++ b/src/ReswPlus.CommandLine/Converters/AndroidXMLConverter.cs @@ -23,17 +23,22 @@ private static IEnumerable ExtractAndroidItems(string sourcePath) { var sourceAndroidXML = XDocument.Load(sourcePath); var xmlRoot = sourceAndroidXML.Root; - string currentComment = null; + if (xmlRoot is null) + { + yield break; + } + + string? currentComment = null; foreach (var xNode in xmlRoot.Nodes()) { switch (xNode) { - case XComment commentNode: + case XComment commentNode when commentNode is { }: { currentComment = RemoveAntislash(commentNode.Value.Trim()); } break; - case XElement elementNode: + case XElement elementNode when elementNode is { }: { switch (elementNode.Name.LocalName.ToLower()) { @@ -49,13 +54,16 @@ private static IEnumerable ExtractAndroidItems(string sourcePath) } case "plurals": { - var name = elementNode.Attribute("name").Value; - foreach (var subItem in elementNode.Elements("item")) + var name = elementNode.Attribute("name")?.Value; + if (name is not null) { - var quantity = subItem.Attribute("quantity")?.Value; - if (quantity != null) + foreach (var subItem in elementNode.Elements("item")) { - yield return new ReswItem($"{name}_{UpperCaseFirstLetter(quantity)}", RemoveAntislash(subItem.Value), currentComment); + var quantity = subItem.Attribute("quantity")?.Value; + if (quantity != null) + { + yield return new ReswItem($"{name}_{UpperCaseFirstLetter(quantity)}", RemoveAntislash(subItem.Value), currentComment); + } } } break; @@ -77,6 +85,11 @@ private static void SaveReswFile(string destinationPath, IEnumerable k { var reswFileXml = XDocument.Parse(_reswTemplate); var templateRoot = reswFileXml.Root; + if (templateRoot is null) + { + return; + } + foreach (var item in keys) { var dataNode = new XElement("data"); @@ -184,7 +197,7 @@ public static bool AndroidXMLDirectoryToResw(string sourcePath, string destinati var indexDash = directoryName.IndexOf('-'); var languageId = directoryName.Substring(indexDash + 1); var generatedFilePath = $"{destinationPath}\\{AndroidToReswLanguageId(languageId)}\\Resources.resw"; - _ = Directory.CreateDirectory(Path.GetDirectoryName(generatedFilePath)); + _ = Directory.CreateDirectory(Path.GetDirectoryName(generatedFilePath)!); if (!AndroidXMLFileToResw(xmlFilePath, generatedFilePath)) { return false; diff --git a/src/ReswPlus.CommandLine/Parameters/AndroidToReswParameters.cs b/src/ReswPlus.CommandLine/Parameters/AndroidToReswParameters.cs index dcf1a62..b22b870 100644 --- a/src/ReswPlus.CommandLine/Parameters/AndroidToReswParameters.cs +++ b/src/ReswPlus.CommandLine/Parameters/AndroidToReswParameters.cs @@ -6,8 +6,8 @@ namespace ReswPlusCmd.Parameters; public class AndroidToReswParameters { [Value(0, HelpText = "Output Directory Path", MetaName = "output")] - public string OutputPath { get; set; } + public string? OutputPath { get; set; } [Option('i', "input", HelpText = "A single XML File or a path of a directory containing xml files", Required = true)] - public string Input { get; set; } + public string? Input { get; set; } } diff --git a/src/ReswPlus.CommandLine/Parameters/ReswToAndroidParameters.cs b/src/ReswPlus.CommandLine/Parameters/ReswToAndroidParameters.cs index 4361f4a..9368b66 100644 --- a/src/ReswPlus.CommandLine/Parameters/ReswToAndroidParameters.cs +++ b/src/ReswPlus.CommandLine/Parameters/ReswToAndroidParameters.cs @@ -6,10 +6,10 @@ namespace ReswPlusCmd.Parameters; public class ReswToAndroidParameters { [Value(0, HelpText = "Output File Path", MetaName = "output")] - public string OutputFilePath { get; set; } + public string? OutputFilePath { get; set; } [Option('i', "input", HelpText = "Resw input file", Required = true)] - public string Input { get; set; } + public string? Input { get; set; } [Option('p', "pluralization", Default = true, HelpText = "boolean indicating if the resw file supports pluralization", Required = false)] public bool SupportPluralization { get; set; } diff --git a/src/ReswPlus.CommandLine/Program.cs b/src/ReswPlus.CommandLine/Program.cs index ffdd0d4..ebf546d 100644 --- a/src/ReswPlus.CommandLine/Program.cs +++ b/src/ReswPlus.CommandLine/Program.cs @@ -29,7 +29,7 @@ private static int AndroidToReswCommand(AndroidToReswParameters parameters) { if (Directory.Exists(parameters.Input)) { - var success = AndroidXMLConverter.AndroidXMLDirectoryToResw(parameters.Input, parameters.OutputPath); + var success = AndroidXMLConverter.AndroidXMLDirectoryToResw(parameters.Input, parameters.OutputPath!); if (success) { Console.WriteLine($"Directory created: {parameters.OutputPath}"); @@ -43,7 +43,7 @@ private static int AndroidToReswCommand(AndroidToReswParameters parameters) } else if (File.Exists(parameters.Input)) { - var success = AndroidXMLConverter.AndroidXMLFileToResw(parameters.Input, parameters.OutputPath); + var success = AndroidXMLConverter.AndroidXMLFileToResw(parameters.Input, parameters.OutputPath!); if (success) { Console.WriteLine($"File created: {parameters.OutputPath}"); @@ -83,7 +83,7 @@ private static int ReswToAndroidCommand(ReswToAndroidParameters parameters) Console.WriteLine($"Error during the conversion of the file: {parameters.Input}"); return -1; } - androidXML.Save(parameters.OutputFilePath); + androidXML.Save(parameters.OutputFilePath!); return 0; } #endregion diff --git a/src/ReswPlus.CommandLine/ReswPlusCmd.csproj b/src/ReswPlus.CommandLine/ReswPlusCmd.csproj index d38697e..65a704c 100644 --- a/src/ReswPlus.CommandLine/ReswPlusCmd.csproj +++ b/src/ReswPlus.CommandLine/ReswPlusCmd.csproj @@ -5,6 +5,7 @@ net8.0 ReswPlusCmd.Program ReswPlusCmd + enable diff --git a/src/ReswPlus.Shared/Interfaces/IErrorLogger.cs b/src/ReswPlus.Shared/Interfaces/IErrorLogger.cs index 8d03a8b..0f56c27 100644 --- a/src/ReswPlus.Shared/Interfaces/IErrorLogger.cs +++ b/src/ReswPlus.Shared/Interfaces/IErrorLogger.cs @@ -2,6 +2,6 @@ namespace ReswPlus.Core.Interfaces; public interface IErrorLogger { - void LogError(string message, string document = null); - void LogWarning(string message, string document = null); + void LogError(string message, string? document = null); + void LogWarning(string message, string? document = null); } diff --git a/src/ReswPlus.Shared/ResourceParser/FormatTag.cs b/src/ReswPlus.Shared/ResourceParser/FormatTag.cs index 8ff26ca..d19f479 100644 --- a/src/ReswPlus.Shared/ResourceParser/FormatTag.cs +++ b/src/ReswPlus.Shared/ResourceParser/FormatTag.cs @@ -11,31 +11,54 @@ public interface IFormatTagParameter } public sealed class StringRefFormatTagParameter : IFormatTagParameter { - public string Id { get; set; } + public string Id { get; } + + public StringRefFormatTagParameter(string id) + { + Id = id; + } } public sealed class LiteralStringFormatTagParameter : IFormatTagParameter { - public string Value { get; set; } + public string Value { get; } + + public LiteralStringFormatTagParameter(string value) + { + Value = value; + } } public sealed class MacroFormatTagParameter : IFormatTagParameter { - public string Id { get; set; } + public string Id { get; } + + public MacroFormatTagParameter(string id) + { + Id = id; + } } public sealed class FunctionFormatTagParameter : IFormatTagParameter { - public ParameterType Type { get; set; } - public string Name { get; set; } - public ParameterType? TypeToCast { get; set; } - public bool IsVariantId { get; set; } + public ParameterType Type { get; } + public string Name { get; } + public ParameterType? TypeToCast { get; } + public bool IsVariantId { get; } + + public FunctionFormatTagParameter(ParameterType type, string name, ParameterType? typeToCast, bool isVariantId) + { + Type = type; + Name = name; + TypeToCast = typeToCast; + IsVariantId = isVariantId; + } } public sealed class FunctionFormatTagParametersInfo { - public List Parameters { get; set; } = []; - public FunctionFormatTagParameter PluralizationParameter { get; set; } - public FunctionFormatTagParameter VariantParameter { get; set; } + public List Parameters { get; set; } = new(); + public FunctionFormatTagParameter? PluralizationParameter { get; set; } + public FunctionFormatTagParameter? VariantParameter { get; set; } } public sealed class FormatTagParameterTypeInfo(ParameterType type, bool canBeQuantifier) @@ -95,7 +118,7 @@ public sealed class FormatTag private static readonly Regex RegexNamedParameters = new("^(?:(?:\"(?(?:\\\\.|[^\\\"])*)\")|(?:(?\\w+)\\(\\))|(?:(?Plural\\s+)?(?\\w+)\\s*(?\\w+)?))$"); - public static FunctionFormatTagParametersInfo ParseParameters(string key, IEnumerable types, IEnumerable basicLocalizedItems, string resourceFilename, IErrorLogger logger) + public static FunctionFormatTagParametersInfo? ParseParameters(string key, IEnumerable types, IEnumerable basicLocalizedItems, string resourceFilename, IErrorLogger? logger) { var result = new FunctionFormatTagParametersInfo(); var paramIndex = 1; @@ -109,11 +132,7 @@ public static FunctionFormatTagParametersInfo ParseParameters(string key, IEnume } if (matchNamedParameters.Groups["literalString"].Success) { - var param = new LiteralStringFormatTagParameter() - { - Value = matchNamedParameters.Groups["literalString"].Value - }; - + var param = new LiteralStringFormatTagParameter(matchNamedParameters.Groups["literalString"].Value); result.Parameters.Add(param); } else @@ -128,10 +147,7 @@ public static FunctionFormatTagParametersInfo ParseParameters(string key, IEnume logger?.LogError($"ReswPlus: Incorrect tag for the key '{key}': '{localizationRef}' doesn't exist in the resw file.", resourceFilename); return null; } - var param = new StringRefFormatTagParameter() - { - Id = localizationRef - }; + var param = new StringRefFormatTagParameter(localizationRef); result.Parameters.Add(param); } @@ -156,7 +172,7 @@ public static FunctionFormatTagParametersInfo ParseParameters(string key, IEnume } else if (!isQuantifier && MacrosAvailable.TryGetValue(paramTypeId, out var macroID) && string.IsNullOrEmpty(paramName)) { - result.Parameters.Add(new MacroFormatTagParameter() { Id = macroID }); + result.Parameters.Add(new MacroFormatTagParameter(macroID)); continue; } @@ -171,8 +187,8 @@ public static FunctionFormatTagParametersInfo ParseParameters(string key, IEnume paramName = paramTypeId == "Variant" ? "variantId" : isQuantifier ? "pluralCount" : "param" + paramType.type + paramIndex; } - var functionParam = new FunctionFormatTagParameter { Type = paramType.type.Value, Name = paramName, TypeToCast = paramType.typeToCast, IsVariantId = paramType.isVariantId }; - if (isQuantifier && result.PluralizationParameter == null) + var functionParam = new FunctionFormatTagParameter(paramType.type.Value, paramName, paramType.typeToCast, paramType.isVariantId); + if (isQuantifier && result.PluralizationParameter is null) { result.PluralizationParameter = functionParam; } diff --git a/src/ReswPlus.Shared/ResourceParser/ReswFilters.cs b/src/ReswPlus.Shared/ResourceParser/ReswFilters.cs index 236f0ad..69185b5 100644 --- a/src/ReswPlus.Shared/ResourceParser/ReswFilters.cs +++ b/src/ReswPlus.Shared/ResourceParser/ReswFilters.cs @@ -6,10 +6,18 @@ namespace ReswPlus.Core.ResourceParser; public sealed class VariantedReswItems { - public List Items { get; set; } - public string Key { get; set; } - public bool SupportPlural { get; set; } - public bool SupportVariants { get; set; } + public List Items { get; } + public string Key { get; } + public bool SupportPlural { get; } + public bool SupportVariants { get; } + + public VariantedReswItems(List items, string key, bool supportPlural, bool supportVariants) + { + Items = items; + Key = key; + SupportPlural = supportPlural; + SupportVariants = supportVariants; + } } public static class ReswFilters @@ -41,13 +49,12 @@ by commonKey into gr continue; } - yield return new VariantedReswItems() - { - Key = variantedItem.Key, - Items = variantedItem.Select(i => i.item).ToList(), - SupportPlural = variantedItem.First().isPlural, - SupportVariants = variantedItem.First().isVariant, - }; + yield return new VariantedReswItems( + variantedItem.Select(i => i.item).ToList(), + variantedItem.Key, + variantedItem.First().isPlural, + variantedItem.First().isVariant + ); } } } diff --git a/src/ReswPlus.Shared/ResourceParser/ReswInfo.cs b/src/ReswPlus.Shared/ResourceParser/ReswInfo.cs index bd6d764..77cd626 100644 --- a/src/ReswPlus.Shared/ResourceParser/ReswInfo.cs +++ b/src/ReswPlus.Shared/ResourceParser/ReswInfo.cs @@ -4,5 +4,10 @@ namespace ReswPlus.Core.ResourceParser; public sealed class ReswInfo { - public List Items { get; set; } + public List Items { get; } + + public ReswInfo() + { + Items = new(); + } } diff --git a/src/ReswPlus.Shared/ResourceParser/ReswItem.cs b/src/ReswPlus.Shared/ResourceParser/ReswItem.cs index e5991c6..5dea05e 100644 --- a/src/ReswPlus.Shared/ResourceParser/ReswItem.cs +++ b/src/ReswPlus.Shared/ResourceParser/ReswItem.cs @@ -1,8 +1,8 @@ namespace ReswPlus.Core.ResourceParser; -public sealed class ReswItem(string key, string value, string comment = null) +public sealed class ReswItem(string key, string value, string? comment = null) { public string Key { get; } = key; public string Value { get; } = value; - public string Comment { get; } = comment; + public string? Comment { get; } = comment; } diff --git a/src/ReswPlus.Shared/ResourceParser/ReswParser.cs b/src/ReswPlus.Shared/ResourceParser/ReswParser.cs index 6d3ca4b..758da27 100644 --- a/src/ReswPlus.Shared/ResourceParser/ReswParser.cs +++ b/src/ReswPlus.Shared/ResourceParser/ReswParser.cs @@ -6,23 +6,20 @@ public sealed class ReswParser { public static ReswInfo Parse(string content) { - var res = new ReswInfo - { - Items = [] - }; + var res = new ReswInfo(); var xml = new XmlDocument(); xml.LoadXml(content); var nodes = xml.DocumentElement?.SelectNodes("//data"); - if (nodes == null) + if (nodes is null) { return res; } foreach (XmlElement element in nodes) { - string comment = null; + string? comment = null; var elementKey = element.Attributes.GetNamedItem("name"); string key; if (elementKey != null) diff --git a/src/ReswPlus.SourceGenerator/ClassGenerators/GenerationResult.cs b/src/ReswPlus.SourceGenerator/ClassGenerators/GenerationResult.cs index 3f3b97a..313db39 100644 --- a/src/ReswPlus.SourceGenerator/ClassGenerators/GenerationResult.cs +++ b/src/ReswPlus.SourceGenerator/ClassGenerators/GenerationResult.cs @@ -22,4 +22,9 @@ internal sealed class GenerationResult /// Gets or sets a value indicating whether the result contains macros. /// public bool ContainsMacro { get; set; } + + public GenerationResult(IEnumerable files) + { + Files = files; + } } diff --git a/src/ReswPlus.SourceGenerator/ClassGenerators/Models/Localization.cs b/src/ReswPlus.SourceGenerator/ClassGenerators/Models/Localization.cs index 9446785..394c0b0 100644 --- a/src/ReswPlus.SourceGenerator/ClassGenerators/Models/Localization.cs +++ b/src/ReswPlus.SourceGenerator/ClassGenerators/Models/Localization.cs @@ -9,25 +9,31 @@ namespace ReswPlus.SourceGenerator.ClassGenerators.Models; /// internal abstract class Localization { + protected Localization(string key, string summary) + { + Key = key; + Summary = summary; + } + /// /// Gets or sets the key for the localization. /// - public string Key { get; set; } + public string Key { get; } /// /// Gets or sets the list of format tag parameters. /// - public List Parameters { get; set; } = []; + public List Parameters { get; set; } = new(); /// /// Gets the list of extra function format tag parameters. /// - public List ExtraParameters { get; } = []; + public List ExtraParameters { get; } = new(); /// /// Gets or sets the summary for the localization. /// - public string Summary { get; set; } + public string Summary { get; } /// /// Gets or sets a value indicating whether .NET formatting is used. @@ -44,13 +50,21 @@ internal abstract class Localization /// Represents a regular localization. /// internal sealed class RegularLocalization : Localization -{ } +{ + public RegularLocalization(string key, string summary) : base(key, summary) + { + } +} /// /// Represents a plural localization. /// internal class PluralLocalization : Localization { + public PluralLocalization(string key, string summary) : base(key, summary) + { + } + /// /// Gets or sets a value indicating whether the none state is supported. /// @@ -59,7 +73,7 @@ internal class PluralLocalization : Localization /// /// Gets or sets the parameter to use for pluralization. /// - public FunctionFormatTagParameter ParameterToUseForPluralization { get; set; } + public FunctionFormatTagParameter? ParameterToUseForPluralization { get; set; } } /// @@ -70,7 +84,7 @@ internal interface IVariantLocalization /// /// Gets or sets the parameter to use for variant. /// - FunctionFormatTagParameter ParameterToUseForVariant { get; set; } + FunctionFormatTagParameter? ParameterToUseForVariant { get; set; } } /// @@ -78,10 +92,14 @@ internal interface IVariantLocalization /// internal sealed class PluralVariantLocalization : PluralLocalization, IVariantLocalization { + public PluralVariantLocalization(string key, string summary) : base(key, summary) + { + } + /// /// Gets or sets the parameter to use for variant. /// - public FunctionFormatTagParameter ParameterToUseForVariant { get; set; } + public FunctionFormatTagParameter? ParameterToUseForVariant { get; set; } } /// @@ -89,8 +107,12 @@ internal sealed class PluralVariantLocalization : PluralLocalization, IVariantLo /// internal sealed class VariantLocalization : Localization, IVariantLocalization { + public VariantLocalization(string key, string summary) : base(key, summary) + { + } + /// /// Gets or sets the parameter to use for variant. /// - public FunctionFormatTagParameter ParameterToUseForVariant { get; set; } + public FunctionFormatTagParameter? ParameterToUseForVariant { get; set; } } diff --git a/src/ReswPlus.SourceGenerator/ClassGenerators/Models/StronglyTypedClass.cs b/src/ReswPlus.SourceGenerator/ClassGenerators/Models/StronglyTypedClass.cs index b3c2bc6..a3bd816 100644 --- a/src/ReswPlus.SourceGenerator/ClassGenerators/Models/StronglyTypedClass.cs +++ b/src/ReswPlus.SourceGenerator/ClassGenerators/Models/StronglyTypedClass.cs @@ -4,11 +4,21 @@ namespace ReswPlus.SourceGenerator.ClassGenerators.Models; internal sealed class StronglyTypedClass { - public bool IsAdvanced { get; set; } - public string[] Namespaces { get; set; } - public string ResoureFile { get; set; } - public string ClassName { get; set; } - public AppType AppType { get; set; } + public StronglyTypedClass(bool isAdvanced, string[] namespaces, string resoureFile, string className, AppType appType) + { + IsAdvanced = isAdvanced; + Namespaces = namespaces; + ResoureFile = resoureFile; + ClassName = className; + AppType = appType; + Items = new(); + } - public List Items { get; set; } = []; + public bool IsAdvanced { get; } + public string[] Namespaces { get; } + public string ResoureFile { get; } + public string ClassName { get; } + public AppType AppType { get; } + + public List Items { get; } } diff --git a/src/ReswPlus.SourceGenerator/ClassGenerators/PluralFormsRetriever.cs b/src/ReswPlus.SourceGenerator/ClassGenerators/PluralFormsRetriever.cs index 038a1e3..8ef8126 100644 --- a/src/ReswPlus.SourceGenerator/ClassGenerators/PluralFormsRetriever.cs +++ b/src/ReswPlus.SourceGenerator/ClassGenerators/PluralFormsRetriever.cs @@ -9,18 +9,25 @@ internal sealed class PluralFormsRetriever { internal record PluralForm { - public string[] Languages { get; set; } + public PluralForm(string id, string[] languages) + { + Id = id; + Languages = languages; + } + public string Id { get; set; } + public string[] Languages { get; set; } } /// /// A static collection of predefined plural forms and their associated languages. /// - private static readonly PluralForm[] PluralForms = - [ - new PluralForm { - Languages = - [ + private static readonly PluralForm[] PluralForms = new PluralForm[] + { + new PluralForm( + "IntOneOrZero", + new[] + { "ak", // Akan "bh", // Bihari "guw", // Gun @@ -29,13 +36,13 @@ internal record PluralForm "nso", // Northern Sotho "pa", // Punjabi "ti", // Tigrinya - "wa" // Walloon - ], - Id = "IntOneOrZero" - }, - new PluralForm { - Languages = - [ + "wa" // Walloon + } + ), + new PluralForm( + "ZeroToOne", + new[] + { "am", // Amharic "bn", // Bengali "ff", // Fulah @@ -45,21 +52,21 @@ internal record PluralForm "mr", // Marathi "fa", // Persian "zu" // Zulu - ], - Id = "ZeroToOne" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "ZeroToTwoExcluded", + new[] + { "hy", // Armenian "fr", // French "kab" // Kabyle - ], - Id = "ZeroToTwoExcluded" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "OnlyOne", + new[] + { "af", // Afrikaans "sq", // Albanian "ast", // Asturian @@ -159,138 +166,138 @@ internal record PluralForm "xh", // Xhosa "yi", // Yiddish "ji" // Jiddish - ], - Id = "OnlyOne" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Sinhala", + new[] + { "si" // Sinhala - ], - Id = "Sinhala" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Latvian", + new[] + { "lv", // Latvian "prg" // Prussian - ], - Id = "Latvian" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Irish", + new[] + { "ga" // Irish - ], - Id = "Irish" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Romanian", + new[] + { "ro", // Romanian "mo" // Moldavian - ], - Id = "Romanian" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Lithuanian", + new[] + { "lt" // Lithuanian - ], - Id = "Lithuanian" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Slavic", + new[] + { "ru", // Russian "uk", // Ukrainian "be" // Belarusian - ], - Id = "Slavic" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Czech", + new[] + { "cs", // Czech "sk" // Slovak - ], - Id = "Czech" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Polish", + new[] + { "pl" // Polish - ], - Id = "Polish" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Slovenian", + new[] + { "sl" // Slovenian - ], - Id = "Slovenian" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Arabic", + new[] + { "ar" // Arabic - ], - Id = "Arabic" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Hebrew", + new[] + { "he", // Hebrew "iw" // (old code for Hebrew) - ], - Id = "Hebrew" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Filipino", + new[] + { "fil", // Filipino "tl" // Tagalog - ], - Id = "Filipino" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Macedonian", + new[] + { "mk" // Macedonian - ], - Id = "Macedonian" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Breizh", + new[] + { "br" // Breton - ], - Id = "Breizh" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "CentralAtlasTamazight", + new[] + { "tzm" // Central Atlas Tamazight - ], - Id = "CentralAtlasTamazight" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "OneOrZero", + new[] + { "ksh" // Colognian - ], - Id = "OneOrZero" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "OneOrZeroToOneExcluded", + new[] + { "lag" // Langi - ], - Id = "OneOrZeroToOneExcluded" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "OneOrTwo", + new[] + { "kw", // Cornish "smn", // Inari Sami "iu", // Inuktitut @@ -300,69 +307,68 @@ internal record PluralForm "smi", // Other Sami languages "sms", // Skolt Sami "sma" // Southern Sami - ], - Id = "OneOrTwo" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Croat", + new[] + { "bs", // Bosnian "hr", // Croatian "sr", // Serbian "sh" // Serbo-Croatian - ], - Id = "Croat" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Tachelhit", + new[] + { "shi" // Tachelhit - ], - Id = "Tachelhit" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Icelandic", + new[] + { "is" // Icelandic - ], - Id = "Icelandic" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Manx", + new[] + { "gv" // Manx - ], - Id = "Manx" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "ScottishGaelic", + new[] + { "gd" // Scottish Gaelic - ], - Id = "ScottishGaelic" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Maltese", + new[] + { "mt" // Maltese - ], - Id = "Maltese" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Welsh", + new[] + { "cy" // Welsh - ], - Id = "Welsh" - }, - new PluralForm { - Languages = - [ + } + ), + new PluralForm( + "Danish", + new[] + { "da" // Danish - ], - Id = "Danish" - } - ]; + } + ) + }; // Prebuild a dictionary that maps each language code to its plural form. private static readonly Dictionary LanguageToPluralForm = BuildLanguageToPluralForm(); diff --git a/src/ReswPlus.SourceGenerator/ClassGenerators/ReswClassGenerator.cs b/src/ReswPlus.SourceGenerator/ClassGenerators/ReswClassGenerator.cs index 9baf568..41b0de2 100644 --- a/src/ReswPlus.SourceGenerator/ClassGenerators/ReswClassGenerator.cs +++ b/src/ReswPlus.SourceGenerator/ClassGenerators/ReswClassGenerator.cs @@ -27,7 +27,7 @@ public sealed class ReswClassGenerator private readonly ResourceFileInfo _resourceFileInfo; private readonly ICodeGenerator _codeGenerator; - private readonly IErrorLogger _logger; + private readonly IErrorLogger? _logger; static ReswClassGenerator() { @@ -36,7 +36,7 @@ static ReswClassGenerator() $@"(?{TagFormat}|{TagFormatDotNet})\[(?(?:""(?:""|[^""])*""|[^\\""])+)\]"); } - private ReswClassGenerator(ResourceFileInfo resourceInfo, ICodeGenerator generator, IErrorLogger logger) + private ReswClassGenerator(ResourceFileInfo resourceInfo, ICodeGenerator generator, IErrorLogger? logger) { _resourceFileInfo = resourceInfo; _codeGenerator = generator; @@ -49,9 +49,9 @@ private ReswClassGenerator(ResourceFileInfo resourceInfo, ICodeGenerator generat /// The resource file information. /// The error logger. /// A new instance of or null if the language is not supported. - internal static ReswClassGenerator CreateGenerator(ResourceFileInfo resourceFileInfo, IErrorLogger logger) + internal static ReswClassGenerator? CreateGenerator(ResourceFileInfo resourceFileInfo, IErrorLogger? logger) { - ICodeGenerator codeGenerator = resourceFileInfo.Project.Language switch + var codeGenerator = resourceFileInfo.Project.Language switch { Language.CSharp => new CSharpCodeGenerator(), _ => null @@ -81,14 +81,13 @@ private StronglyTypedClass Parse(string content, string defaultNamespace, bool i ? className : $"{projectNameIfLibrary}/{className}"; - var result = new StronglyTypedClass - { - IsAdvanced = isAdvanced, - ClassName = className, - Namespaces = namespacesToUse, - ResoureFile = resourceLoaderName, - AppType = appType - }; + var result = new StronglyTypedClass( + isAdvanced, + namespacesToUse, + resourceLoaderName, + className, + appType + ); // Only use items with valid keys and that do not contain the ignore tag. var stringItems = reswInfo.Items @@ -111,8 +110,8 @@ private StronglyTypedClass Parse(string content, string defaultNamespace, bool i var summary = $"Get the pluralized version of the string similar to: {singleLineValue}"; Localization localization = item.SupportVariants - ? new PluralVariantLocalization { Key = itemKey, Summary = summary, SupportNoneState = hasNoneForm } - : new PluralLocalization { Key = itemKey, Summary = summary, SupportNoneState = hasNoneForm }; + ? new PluralVariantLocalization(itemKey, summary) { SupportNoneState = hasNoneForm } + : new PluralLocalization(itemKey, summary) { SupportNoneState = hasNoneForm }; if (item.Items.Any(i => i.Comment?.Contains(Deprecated_TagStrongType) == true)) { @@ -130,7 +129,7 @@ private StronglyTypedClass Parse(string content, string defaultNamespace, bool i var summary = $"Get the variant version of the string similar to: {singleLineValue}"; var commentToUse = item.Items.FirstOrDefault(i => !string.IsNullOrEmpty(i.Comment) && _regexStringFormat.IsMatch(i.Comment))?.Comment; - var localization = new VariantLocalization { Key = itemKey, Summary = summary }; + var localization = new VariantLocalization(itemKey, summary); ManageFormattedFunction(localization, commentToUse, basicItems, resourceFileName); result.Items.Add(localization); } @@ -146,7 +145,7 @@ private StronglyTypedClass Parse(string content, string defaultNamespace, bool i { var singleLineValue = _regexRemoveSpace.Replace(item.Value, " ").Trim(); var summary = $"Looks up a localized string similar to: {singleLineValue}"; - var localization = new RegularLocalization { Key = item.Key, Summary = summary }; + var localization = new RegularLocalization(item.Key, summary); if (isAdvanced) { @@ -188,16 +187,16 @@ private static bool IsValidPropertyName(string propertyName) /// Indicates whether advanced features are enabled. /// The type of the application. /// A containing the generated files. - internal GenerationResult GenerateCode(string baseFilename, string content, string defaultNamespace, bool isAdvanced, AppType appType) + internal GenerationResult? GenerateCode(string baseFilename, string content, string defaultNamespace, bool isAdvanced, AppType appType) { var stronglyTypedClassInfo = Parse(content, defaultNamespace, isAdvanced, appType); - if (stronglyTypedClassInfo == null) + if (stronglyTypedClassInfo is null) { return null; } var filesGenerated = _codeGenerator.GetGeneratedFiles(baseFilename, stronglyTypedClassInfo, _resourceFileInfo); - var result = new GenerationResult { Files = filesGenerated }; + var result = new GenerationResult(filesGenerated); if (filesGenerated?.Any() == true) { @@ -232,7 +231,7 @@ private string[] ExtractNamespace(string defaultNamespace) /// /// The comment containing the format tag. /// A tuple containing the format string and a boolean indicating if it is .NET formatting. - public static (string format, bool isDotNetFormatting) ParseTag(string comment) + public static (string? format, bool isDotNetFormatting) ParseTag(string? comment) { if (!string.IsNullOrWhiteSpace(comment)) { @@ -254,9 +253,9 @@ public static (string format, bool isDotNetFormatting) ParseTag(string comment) /// The basic localized items. /// The name of the resource. /// True if the function was managed successfully; otherwise, false. - private bool ManageFormattedFunction(Localization localization, string comment, IEnumerable basicLocalizedItems, string resourceName) + private bool ManageFormattedFunction(Localization localization, string? comment, IEnumerable basicLocalizedItems, string resourceName) { - FunctionFormatTagParametersInfo tagTypedInfo = null; + FunctionFormatTagParametersInfo? tagTypedInfo = null; var (format, isDotNetFormatting) = ParseTag(comment); if (format != null) { @@ -272,14 +271,8 @@ private bool ManageFormattedFunction(Localization localization, string comment, if (localization is IVariantLocalization variantLocalization) { // If a variant parameter was not provided via the format tag, add a default. - var variantParameter = tagTypedInfo?.VariantParameter ?? new FunctionFormatTagParameter - { - Type = ParameterType.Long, - Name = "variantId", - IsVariantId = true - }; - - if (tagTypedInfo?.VariantParameter == null) + var variantParameter = tagTypedInfo?.VariantParameter ?? new FunctionFormatTagParameter(ParameterType.Long, "variantId", null, true); + if (tagTypedInfo?.VariantParameter is null) { localization.ExtraParameters.Add(variantParameter); } @@ -289,13 +282,13 @@ private bool ManageFormattedFunction(Localization localization, string comment, if (localization is PluralLocalization pluralLocalization) { // If pluralization parameter was not provided via the format tag, add a default. - var pluralizationParameter = tagTypedInfo?.PluralizationParameter ?? new FunctionFormatTagParameter - { - Type = ParameterType.Double, - Name = "pluralizationReferenceNumber" - }; + var pluralizationParameter = tagTypedInfo?.PluralizationParameter ?? new FunctionFormatTagParameter( + ParameterType.Double, + "pluralizationReferenceNumber", + null, + false); - if (tagTypedInfo?.PluralizationParameter == null) + if (tagTypedInfo?.PluralizationParameter is null) { pluralLocalization.ExtraParameters.Add(pluralizationParameter); } diff --git a/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs b/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs index c536e32..f8ac048 100644 --- a/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs +++ b/src/ReswPlus.SourceGenerator/CodeGenerators/CsharpCodeGenerator.cs @@ -18,7 +18,7 @@ internal sealed class CSharpCodeGenerator : ICodeGenerator /// The strongly-typed class information used for generating code. /// The resource file information used to generate the C# files. /// A collection of generated files. - public IEnumerable GetGeneratedFiles(string baseFilename, StronglyTypedClass info, ResourceFileInfo resourceFileInfo) + public IEnumerable GetGeneratedFiles(string? baseFilename, StronglyTypedClass info, ResourceFileInfo resourceFileInfo) { var builder = new CodeStringBuilder(resourceFileInfo.Project.GetIndentString()); GenerateHeaders(builder, info.IsAdvanced, info.AppType); @@ -78,9 +78,9 @@ public IEnumerable GetGeneratedFiles(string baseFilename, Strongl /// The code string builder containing the generated C# code. /// The base filename for the generated file. /// An enumerable containing the generated file. - private IEnumerable GetGeneratedFiles(CodeStringBuilder builder, string baseFilename) + private IEnumerable GetGeneratedFiles(CodeStringBuilder builder, string? baseFilename) { - yield return new GeneratedFile { Filename = baseFilename + ".cs", Content = builder.GetString() }; + yield return new GeneratedFile(baseFilename + ".cs", builder.GetString()); } /// @@ -142,7 +142,7 @@ private void CloseNamespace(CodeStringBuilder builder, IEnumerable names /// The code string builder to append the class block to. /// The resource file name associated with the strongly-typed class. /// The name of the strongly-typed class. - private void OpenStronglyTypedClass(CodeStringBuilder builder, string resourceFileName, string className) + private void OpenStronglyTypedClass(CodeStringBuilder builder, string? resourceFileName, string className) { var assembly = typeof(CSharpCodeGenerator).Assembly; builder.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"{assembly.GetName().Name}\", \"{assembly.GetName().Version}\")]") @@ -176,7 +176,7 @@ private void CloseStronglyTypedClass(CodeStringBuilder builder) => /// /// The code string builder to append the region block to. /// The name of the region. - private void OpenRegion(CodeStringBuilder builder, string name) => + private void OpenRegion(CodeStringBuilder builder, string? name) => builder.AppendLine($"#region {name}"); /// @@ -184,7 +184,7 @@ private void OpenRegion(CodeStringBuilder builder, string name) => /// /// The code string builder to append the closing region block to. /// The name of the region. - private void CloseRegion(CodeStringBuilder builder, string name) => + private void CloseRegion(CodeStringBuilder builder, string? name) => builder.AppendLine("#endregion"); /// @@ -204,11 +204,11 @@ private void CreateFormatMethod( string key, bool isProperty, IEnumerable parameters, - string summary = null, - IEnumerable extraParameters = null, - FunctionFormatTagParameter parameterForPluralization = null, + string? summary = null, + IEnumerable? extraParameters = null, + FunctionFormatTagParameter? parameterForPluralization = null, bool supportNoneState = false, - FunctionFormatTagParameter parameterForVariant = null) + FunctionFormatTagParameter? parameterForVariant = null) { // Documentation header. builder.AppendLine("/// ") @@ -225,7 +225,7 @@ private void CreateFormatMethod( else { // Combine parameters: start with extra parameters (if any) and then add regular parameters. - var functionParameters = parameters?.OfType().ToList() ?? new List(); + var functionParameters = parameters?.OfType().ToList() ?? new(); if (extraParameters?.Any() == true) { functionParameters.InsertRange(0, extraParameters); @@ -316,7 +316,7 @@ private void CreateFormatMethod( /// The name of the resource file associated with the class. /// The name of the markup extension class. /// The collection of keys for the strongly-typed resource class. - private void CreateMarkupExtension(CodeStringBuilder builder, string resourceFileName, string className, IEnumerable keys) + private void CreateMarkupExtension(CodeStringBuilder builder, string? resourceFileName, string? className, IEnumerable keys) { var assembly = typeof(CSharpCodeGenerator).Assembly; builder.AppendLine($"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"{assembly.GetName().Name}\", \"{assembly.GetName().Version}\")]") diff --git a/src/ReswPlus.SourceGenerator/CodeGenerators/ICodeGenerator.cs b/src/ReswPlus.SourceGenerator/CodeGenerators/ICodeGenerator.cs index 1ac3d22..5386ae4 100644 --- a/src/ReswPlus.SourceGenerator/CodeGenerators/ICodeGenerator.cs +++ b/src/ReswPlus.SourceGenerator/CodeGenerators/ICodeGenerator.cs @@ -9,20 +9,26 @@ namespace ReswPlus.SourceGenerator.CodeGenerators; /// internal sealed class GeneratedFile { + public GeneratedFile(string filename, string content) + { + Filename = filename; + Content = content; + } + /// /// Gets or sets the filename of the generated file. /// - public string Filename { get; set; } + public string Filename { get; } /// /// Gets or sets the content of the generated file. /// - public string Content { get; set; } + public string Content { get; } /// /// Gets or sets the languages supported by the generated file. /// - public string[] Languages { get; set; } + public string[]? Languages { get; set; } } /// diff --git a/src/ReswPlus.SourceGenerator/ReswGenerator.cs b/src/ReswPlus.SourceGenerator/ReswGenerator.cs index bbb4e64..5ee02a6 100644 --- a/src/ReswPlus.SourceGenerator/ReswGenerator.cs +++ b/src/ReswPlus.SourceGenerator/ReswGenerator.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.Text; using ReswPlus.SourceGenerator.ClassGenerators; using ReswPlus.SourceGenerator.Models; +using Microsoft.CodeAnalysis.Diagnostics; #if DEBUG using System.Diagnostics; @@ -23,8 +24,8 @@ public enum AppType UWP, } -[Generator(LanguageNames.CSharp)] -public partial class ReswSourceGenerator : ISourceGenerator +[Generator] +public partial class ReswSourceGenerator : IIncrementalGenerator { // Diagnostic descriptors (could be moved to a central location if reused) private static readonly DiagnosticDescriptor UnsupportedLanguageDiagnostic = @@ -72,12 +73,7 @@ public partial class ReswSourceGenerator : ISourceGenerator DiagnosticSeverity.Error, isEnabledByDefault: true); - public void Initialize(GeneratorInitializationContext context) - { - // No initialization needed for now. - } - - public void Execute(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { #if DEBUG if (!Debugger.IsAttached) @@ -87,165 +83,211 @@ public void Execute(GeneratorExecutionContext context) } #endif - // Only support C# - if (context.Compilation is not CSharpCompilation) - { - context.ReportDiagnostic(Diagnostic.Create(UnsupportedLanguageDiagnostic, null)); - return; - } - - // Retrieve project root path. - if (!TryGetGlobalOption(context, "build_property.projectdir", out var projectRootPath) - && TryGetGlobalOption(context, "build_property.MSBuildProjectFullPath", out var projectFileFullPath)) + // Create a provider for global analyzer config options. + var globalOptionsProvider = context.AnalyzerConfigOptionsProvider.Select((options, cancellationToken) => new { - projectRootPath = Path.GetDirectoryName(projectFileFullPath); - } - - if (string.IsNullOrEmpty(projectRootPath)) + ProjectDir = GetOption(options.GlobalOptions, "build_property.projectdir"), + MSBuildProjectFullPath = GetOption(options.GlobalOptions, "build_property.MSBuildProjectFullPath"), + OutputType = GetOption(options.GlobalOptions, "build_property.OutputType"), + ProjectTypeGuids = GetOption(options.GlobalOptions, "build_property.projecttypeguids"), + DefaultLanguage = GetOption(options.GlobalOptions, "build_property.DefaultLanguage"), + RootNamespace = GetOption(options.GlobalOptions, "build_property.RootNamespace") + }); + + // Provider for additional files with .resw extension. + var reswFilesProvider = context.AdditionalTextsProvider + .Where(file => Path.GetExtension(file.Path).Equals(".resw", StringComparison.OrdinalIgnoreCase)) + .Collect(); + + // Combine the Compilation, the global options, and the additional files. + var combinedProvider = context.CompilationProvider + .Combine(globalOptionsProvider) + .Combine(reswFilesProvider); + + context.RegisterSourceOutput(combinedProvider, (spc, source) => { - context.ReportDiagnostic(Diagnostic.Create(MissingRootPathDiagnostic, null)); - return; - } - - // Determine if the project is a library. - bool isLibrary = false; - if (TryGetGlobalOption(context, "build_property.OutputType", out var outputType)) - { - isLibrary = outputType.Equals("library", StringComparison.OrdinalIgnoreCase) - || outputType.Equals("module", StringComparison.OrdinalIgnoreCase); - } - else if (TryGetGlobalOption(context, "build_property.projecttypeguids", out var projectTypeGuidsValue)) - { - isLibrary = projectTypeGuidsValue.Equals("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}", StringComparison.OrdinalIgnoreCase) - || projectTypeGuidsValue.Equals("{BC8A1FFA-BEE3-4634-8014-F334798102B3}", StringComparison.OrdinalIgnoreCase); - } - else - { - // If unable to determine, assume it is an application. - context.ReportDiagnostic(Diagnostic.Create(UnknownProjectTypeDiagnostic, Location.None)); - } - - // Determine AppType based on referenced assemblies. - var appType = RetrieveAppType(context); - var assemblyName = Assembly.GetExecutingAssembly().GetName().Name; + // Unpack the combined tuple. + var ((compilation, options), additionalFiles) = source; - switch (appType) - { - case AppType.WindowsAppSDK: - AddSourceFromResource(context, $"{assemblyName}.Templates.ResourceStringProviders.MicrosoftResourceStringProvider.txt", "ResourceStringProvider.cs"); - break; - case AppType.UWP: - AddSourceFromResource(context, $"{assemblyName}.Templates.ResourceStringProviders.WindowsResourceStringProvider.txt", "ResourceStringProvider.cs"); - break; - default: - context.ReportDiagnostic(Diagnostic.Create(UnrecognizedAppTypeDiagnostic, null)); + if (compilation is null || options is null) + { return; - } - - // Retrieve the default language (optional) - TryGetGlobalOption(context, "build_property.DefaultLanguage", out var projectDefaultLanguage); + } - // Retrieve the project's root namespace. - if (!TryGetGlobalOption(context, "build_property.RootNamespace", out var projectRootNamespace)) - { - context.ReportDiagnostic(Diagnostic.Create(UnknownNamespaceDiagnostic, null)); - return; - } + // Only support C# + if (compilation is not CSharpCompilation) + { + spc.ReportDiagnostic(Diagnostic.Create(UnsupportedLanguageDiagnostic, Location.None)); + return; + } - // Retrieve all .resw files from AdditionalFiles. - var allResourceFiles = context.AdditionalFiles - .Where(f => Path.GetExtension(f.Path).Equals(".resw", StringComparison.OrdinalIgnoreCase)) - .Distinct() - .ToArray(); - - // Group files and retrieve the default resource file per group. - var defaultLanguageResourceFiles = (from file in allResourceFiles - group file by - Path.Combine( - Path.GetDirectoryName(Path.GetDirectoryName(file.Path)), - Path.GetFileName(file.Path)) - into fileGroup - select RetrieveDefaultResourceFile( - fileGroup.Select(f => f.Path), - projectDefaultLanguage)) - .ToArray(); - - // Gather all distinct languages. - var allLanguages = allResourceFiles - .Select(f => Path.GetFileName(Path.GetDirectoryName(f.Path)).Split('-')[0].ToLower()) - .Distinct() - .ToArray(); - - // Process each default resource file. - foreach (var file in defaultLanguageResourceFiles) - { - // Determine namespace for the generated class. - var namespaceForReswFile = projectRootNamespace; - var reswParentDirectory = Path.GetDirectoryName(file); - if (reswParentDirectory != null && reswParentDirectory.StartsWith(projectRootPath, StringComparison.OrdinalIgnoreCase)) + // Retrieve project root path. + var projectRootPath = options.ProjectDir; + if (projectRootPath is not { Length: > 0 } && options.MSBuildProjectFullPath is { Length: > 0 }) { - var additionalNamespace = reswParentDirectory.Substring(projectRootPath.Length) - .Trim(Path.DirectorySeparatorChar) - .Replace(Path.DirectorySeparatorChar, '.'); - if (!string.IsNullOrEmpty(additionalNamespace)) - { - namespaceForReswFile += "." + additionalNamespace; - } + projectRootPath = Path.GetDirectoryName(options.MSBuildProjectFullPath); } - // Get the additional file text. - var additionalText = context.AdditionalFiles.FirstOrDefault(f => f.Path == file); - if (additionalText is null) + if (string.IsNullOrEmpty(projectRootPath)) { - continue; + spc.ReportDiagnostic(Diagnostic.Create(MissingRootPathDiagnostic, Location.None)); + return; } - // Generate code for the resource file. - var resourceFileInfo = new ResourceFileInfo(file, new Project(context.Compilation.AssemblyName, isLibrary)); - var codeGenerator = ReswClassGenerator.CreateGenerator(resourceFileInfo, null); - var baseFilename = Path.GetFileName(file).Split('.')[0]; - var generatedData = codeGenerator.GenerateCode( - baseFilename: baseFilename, - content: additionalText.GetText(context.CancellationToken).ToString(), - defaultNamespace: namespaceForReswFile, - isAdvanced: true, - appType: appType); - - // Add each generated file as a new source. - foreach (var generatedFile in generatedData.Files) + // Determine if the project is a library. + bool isLibrary = false; + if (options.OutputType is { Length: > 0 }) + { + isLibrary = options.OutputType.Equals("library", StringComparison.OrdinalIgnoreCase) + || options.OutputType.Equals("module", StringComparison.OrdinalIgnoreCase); + } + else if (options.ProjectTypeGuids is { Length: > 0 }) + { + isLibrary = options.ProjectTypeGuids.Equals("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}", StringComparison.OrdinalIgnoreCase) + || options.ProjectTypeGuids.Equals("{BC8A1FFA-BEE3-4634-8014-F334798102B3}", StringComparison.OrdinalIgnoreCase); + } + else { - context.AddSource($"{Path.GetFileName(file)}.cs", SourceText.From(generatedFile.Content, Encoding.UTF8)); + spc.ReportDiagnostic(Diagnostic.Create(UnknownProjectTypeDiagnostic, Location.None)); } - // If macros were used, include the Macros source file. - if (generatedData.ContainsMacro) + // Determine AppType based on referenced assemblies. + var appType = RetrieveAppType(compilation); + var assemblyName = Assembly.GetExecutingAssembly().GetName().Name; + + switch (appType) { - AddSourceFromResource(context, "ReswPlus.SourceGenerator.Templates.Macros.Macros.txt", "Macros.cs"); + case AppType.WindowsAppSDK: + AddSourceFromResource(spc, $"{assemblyName}.Templates.ResourceStringProviders.MicrosoftResourceStringProvider.txt", "ResourceStringProvider.cs"); + break; + case AppType.UWP: + AddSourceFromResource(spc, $"{assemblyName}.Templates.ResourceStringProviders.WindowsResourceStringProvider.txt", "ResourceStringProvider.cs"); + break; + default: + spc.ReportDiagnostic(Diagnostic.Create(UnrecognizedAppTypeDiagnostic, Location.None)); + return; } - // If plural forms are detected, add plural-related support sources. - if (generatedData.ContainsPlural) + // Retrieve the default language (optional) + var projectDefaultLanguage = options.DefaultLanguage; + + // Retrieve the project's root namespace. + if (string.IsNullOrEmpty(options.RootNamespace)) { - AddSourceFromResource(context, $"{assemblyName}.Templates.Plurals.IPluralProvider.txt", "IPluralProvider.cs"); - AddSourceFromResource(context, $"{assemblyName}.Templates.Plurals.PluralTypeEnum.txt", "PluralTypeEnum.cs"); - AddSourceFromResource(context, $"{assemblyName}.Templates.Utils.IntExt.txt", "IntExt.cs"); - AddSourceFromResource(context, $"{assemblyName}.Templates.Utils.DoubleExt.txt", "DoubleExt.cs"); - AddLanguageSupport(context, allLanguages); + spc.ReportDiagnostic(Diagnostic.Create(UnknownNamespaceDiagnostic, Location.None)); + return; } - } + var projectRootNamespace = options.RootNamespace!; + + // Process all .resw additional files. + var allResourceFiles = additionalFiles.Distinct().ToArray(); + + // Group files and retrieve the default resource file per group. + var defaultLanguageResourceFiles = (from file in allResourceFiles + group file by + Path.Combine( + Path.GetDirectoryName(Path.GetDirectoryName(file.Path)), + Path.GetFileName(file.Path)) + into fileGroup + let defaultFile = RetrieveDefaultResourceFile( + fileGroup.Select(f => f.Path), + projectDefaultLanguage) + where defaultFile != null + select defaultFile).ToArray(); + + // Gather all distinct languages. + var allLanguages = allResourceFiles + .Select(f => Path.GetFileName(Path.GetDirectoryName(f.Path)).Split('-')[0].ToLower()) + .Distinct() + .ToArray(); + + // Process each default resource file. + foreach (var filePath in defaultLanguageResourceFiles) + { + // Determine namespace for the generated class. + var namespaceForReswFile = projectRootNamespace; + var reswParentDirectory = Path.GetDirectoryName(filePath); + if (reswParentDirectory != null && reswParentDirectory.StartsWith(projectRootPath, StringComparison.OrdinalIgnoreCase)) + { + var additionalNamespace = reswParentDirectory.Substring(projectRootPath!.Length) + .Trim(Path.DirectorySeparatorChar) + .Replace(Path.DirectorySeparatorChar, '.'); + if (!string.IsNullOrEmpty(additionalNamespace)) + { + namespaceForReswFile += "." + additionalNamespace; + } + } + + // Get the additional file matching this path. + var additionalText = allResourceFiles.FirstOrDefault(f => f.Path == filePath); + if (additionalText is null) + { + continue; + } + + // Generate code for the resource file. + var resourceFileInfo = new ResourceFileInfo(filePath, new Project(compilation.AssemblyName!, isLibrary)); + var codeGenerator = ReswClassGenerator.CreateGenerator(resourceFileInfo, null); + if (codeGenerator is null) + { + continue; + } + + var baseFilename = Path.GetFileName(filePath).Split('.')[0]; + var text = additionalText.GetText(spc.CancellationToken)?.ToString() ?? ""; + var generatedData = codeGenerator.GenerateCode( + baseFilename: baseFilename, + content: text, + defaultNamespace: namespaceForReswFile, + isAdvanced: true, + appType: appType); + + if (generatedData is null) + { + continue; + } + + // Add each generated file as a new source. + foreach (var generatedFile in generatedData.Files) + { + spc.AddSource($"{Path.GetFileName(filePath)}.cs", SourceText.From(generatedFile.Content, Encoding.UTF8)); + } + + // If macros were used, include the Macros source file. + if (generatedData.ContainsMacro) + { + AddSourceFromResource(spc, "ReswPlus.SourceGenerator.Templates.Macros.Macros.txt", "Macros.cs"); + } + + // If plural forms are detected, add plural-related support sources. + if (generatedData.ContainsPlural) + { + AddSourceFromResource(spc, $"{assemblyName}.Templates.Plurals.IPluralProvider.txt", "IPluralProvider.cs"); + AddSourceFromResource(spc, $"{assemblyName}.Templates.Plurals.PluralTypeEnum.txt", "PluralTypeEnum.cs"); + AddSourceFromResource(spc, $"{assemblyName}.Templates.Utils.IntExt.txt", "IntExt.cs"); + AddSourceFromResource(spc, $"{assemblyName}.Templates.Utils.DoubleExt.txt", "DoubleExt.cs"); + AddLanguageSupport(spc, allLanguages); + } + } + }); + } + + /// + /// Helper method to retrieve an option value. + /// + private static string? GetOption(AnalyzerConfigOptions globalOptions, string key) + { + return globalOptions.TryGetValue(key, out var value) ? value : null; } /// /// Retrieve the default resource file from the given list that matches one of the preferred languages. /// - /// Collection of resource file paths. - /// The default language of the project. - /// The path to the selected default resource file, or null if none. - private string RetrieveDefaultResourceFile(IEnumerable reswFiles, string defaultLanguage) + private static string? RetrieveDefaultResourceFile(IEnumerable reswFiles, string? defaultLanguage) { // Build a list of candidate languages. var candidateLanguages = new List(); - if (!string.IsNullOrEmpty(defaultLanguage)) + if (defaultLanguage is { Length: > 0 }) { candidateLanguages.Add(defaultLanguage); } @@ -282,12 +324,12 @@ private string RetrieveDefaultResourceFile(IEnumerable reswFiles, string /// /// Determines the application type (WindowsAppSDK, UWP, or Unknown) by inspecting the compilation's external references. /// - private AppType RetrieveAppType(GeneratorExecutionContext context) + private static AppType RetrieveAppType(Compilation compilation) { - return context.Compilation.ExternalReferences.Any(r => + return compilation.ExternalReferences.Any(r => r.Display?.IndexOf("Microsoft.WindowsAppSdk", StringComparison.OrdinalIgnoreCase) >= 0) ? AppType.WindowsAppSDK - : context.Compilation.ExternalReferences.Any(r => + : compilation.ExternalReferences.Any(r => r.Display?.IndexOf("Windows.Foundation.UniversalApiContract", StringComparison.OrdinalIgnoreCase) >= 0) ? AppType.UWP : AppType.Unknown; @@ -296,7 +338,7 @@ private AppType RetrieveAppType(GeneratorExecutionContext context) /// /// Adds language support sources for pluralization based on the provided languages. /// - private static void AddLanguageSupport(GeneratorExecutionContext context, string[] languagesSupported) + private static void AddLanguageSupport(SourceProductionContext spc, string[] languagesSupported) { var pluralSelectorCode = "default:\n return new _ReswPlus_AutoGenerated.Plurals.OtherProvider();\n"; var assemblyName = Assembly.GetExecutingAssembly().GetName().Name; @@ -304,7 +346,7 @@ private static void AddLanguageSupport(GeneratorExecutionContext context, string foreach (var pluralFile in PluralFormsRetriever.RetrievePluralFormsForLanguages(languagesSupported)) { var resourceName = $"{assemblyName}.Templates.Plurals.{pluralFile.Id}Provider.txt"; - AddSourceFromResource(context, resourceName, $"{pluralFile.Id}Provider.cs"); + AddSourceFromResource(spc, resourceName, $"{pluralFile.Id}Provider.cs"); // Add each language handled by this provider. foreach (var lng in pluralFile.Languages) @@ -315,28 +357,28 @@ private static void AddLanguageSupport(GeneratorExecutionContext context, string } // Add the fallback provider. - AddSourceFromResource(context, $"{assemblyName}.Templates.Plurals.OtherProvider.txt", "OtherProvider.cs"); + AddSourceFromResource(spc, $"{assemblyName}.Templates.Plurals.OtherProvider.txt", "OtherProvider.cs"); // Build and add the ResourceLoaderExtension with the plural selector injected. var resourceLoaderResourceName = $"{assemblyName}.Templates.Plurals.ResourceLoaderExtension.txt"; var resourceLoaderTemplate = ReadAllText(Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceLoaderResourceName)); var resourceLoaderCode = resourceLoaderTemplate.Replace("{{PluralProviderSelector}}", pluralSelectorCode); - context.AddSource("ResourceLoaderExtension.cs", SourceText.From(resourceLoaderCode, Encoding.UTF8)); + spc.AddSource("ResourceLoaderExtension.cs", SourceText.From(resourceLoaderCode, Encoding.UTF8)); } /// /// Reads a resource stream and adds its content as a source file. /// - private static void AddSourceFromResource(GeneratorExecutionContext context, string resourcePath, string itemName) + private static void AddSourceFromResource(SourceProductionContext spc, string resourcePath, string itemName) { using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath); if (stream is null) { - // You might want to report a diagnostic or throw if the resource is missing. + // Optionally, report a diagnostic or throw if the resource is missing. return; } var sourceText = ReadAllText(stream); - context.AddSource(itemName, SourceText.From(sourceText, Encoding.UTF8)); + spc.AddSource(itemName, SourceText.From(sourceText, Encoding.UTF8)); } /// @@ -348,12 +390,4 @@ private static string ReadAllText(Stream stream) using var reader = new StreamReader(stream); return reader.ReadToEnd(); } - - /// - /// Attempts to retrieve a global analyzer config option. - /// - private static bool TryGetGlobalOption(GeneratorExecutionContext context, string key, out string value) - { - return context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(key, out value); - } } diff --git a/src/ReswPlus.SourceGenerator/ReswPlus.SourceGenerator.csproj b/src/ReswPlus.SourceGenerator/ReswPlus.SourceGenerator.csproj index 9266416..aaee5c8 100644 --- a/src/ReswPlus.SourceGenerator/ReswPlus.SourceGenerator.csproj +++ b/src/ReswPlus.SourceGenerator/ReswPlus.SourceGenerator.csproj @@ -2,36 +2,20 @@ netstandard2.0 - 12.0 + enable + 12.0 + true true false true + true + Generated - - - full - true - - - - - - - - - - - - - - - - @@ -69,10 +53,7 @@ - - - - + diff --git a/src/ReswPlus.SourceGenerator/Templates/Macros/Macros.txt b/src/ReswPlus.SourceGenerator/Templates/Macros/Macros.txt index 2039efc..74a03e3 100644 --- a/src/ReswPlus.SourceGenerator/Templates/Macros/Macros.txt +++ b/src/ReswPlus.SourceGenerator/Templates/Macros/Macros.txt @@ -21,7 +21,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_shortDate == null) + if (_shortDate is null) { _shortDate = DateTime.Now.ToString("d"); } @@ -36,7 +36,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_longDate == null) + if (_longDate is null) { _longDate = DateTime.Now.ToString("D"); } @@ -51,7 +51,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_shortTime == null) + if (_shortTime is null) { _shortTime = DateTime.Now.ToString("t"); } @@ -66,7 +66,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_longTime == null) + if (_longTime is null) { _longTime = DateTime.Now.ToString("T"); } @@ -81,7 +81,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_weekDay == null) + if (_weekDay is null) { _weekDay = DateTime.Now.ToString("dddd"); } @@ -96,7 +96,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_shortWeekDay == null) + if (_shortWeekDay is null) { _shortWeekDay = DateTime.Now.ToString("ddd"); } @@ -111,7 +111,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_year == null) + if (_year is null) { _year = DateTime.Now.ToString("yyyy"); } @@ -126,7 +126,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_yearTwoDigits == null) + if (_yearTwoDigits is null) { _yearTwoDigits = DateTime.Now.ToString("y"); } @@ -141,7 +141,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_localeName == null) + if (_localeName is null) { _localeName = CultureInfo.CurrentUICulture.DisplayName; } @@ -156,7 +156,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_localeId == null) + if (_localeId is null) { _localeId = CultureInfo.CurrentUICulture.Name; } @@ -171,7 +171,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_localeTwoLetters == null) + if (_localeTwoLetters is null) { _localeTwoLetters = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; } @@ -188,7 +188,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_appVersionFull == null) + if (_appVersionFull is null) { #if WINDOWS_UWP || NETCOREAPP var version = Package.Current.Id.Version; @@ -211,7 +211,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_appVersionMajorMinorBuild == null) + if (_appVersionMajorMinorBuild is null) { #if WINDOWS_UWP || NETCOREAPP var version = Package.Current.Id.Version; @@ -234,7 +234,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_appVersionMajorMinor == null) + if (_appVersionMajorMinor is null) { #if WINDOWS_UWP || NETCOREAPP var version = Package.Current.Id.Version; @@ -257,7 +257,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_appVersionMajor == null) + if (_appVersionMajor is null) { #if WINDOWS_UWP || NETCOREAPP var version = Package.Current.Id.Version; @@ -280,7 +280,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_architecture == null) + if (_architecture is null) { switch (Package.Current.Id.Architecture) { @@ -318,7 +318,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_applicationName == null) + if (_applicationName is null) { #if WINDOWS_UWP || NETCOREAPP _applicationName = Package.Current.DisplayName; @@ -340,7 +340,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_publisherName == null) + if (_publisherName is null) { _publisherName = Package.Current.PublisherDisplayName; } @@ -357,7 +357,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_deviceFamily == null) + if (_deviceFamily is null) { _deviceFamily = AnalyticsInfo.VersionInfo.DeviceFamily; } @@ -374,7 +374,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_deviceManufacturer == null) + if (_deviceManufacturer is null) { _deviceManufacturer = new EasClientDeviceInformation().SystemManufacturer; } @@ -391,7 +391,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_deviceModel == null) + if (_deviceModel is null) { _deviceModel = new EasClientDeviceInformation().SystemProductName; } @@ -408,7 +408,7 @@ namespace _ReswPlus_AutoGenerated { get { - if (_operatingSystemVersion == null) + if (_operatingSystemVersion is null) { ulong version = ulong.Parse(AnalyticsInfo.VersionInfo.DeviceFamilyVersion); var major = (ushort)((version & 0xFFFF000000000000L) >> 48); diff --git a/src/ReswPlus.SourceGenerator/Templates/Plurals/ResourceLoaderExtension.txt b/src/ReswPlus.SourceGenerator/Templates/Plurals/ResourceLoaderExtension.txt index df8d2b8..db8a57f 100644 --- a/src/ReswPlus.SourceGenerator/Templates/Plurals/ResourceLoaderExtension.txt +++ b/src/ReswPlus.SourceGenerator/Templates/Plurals/ResourceLoaderExtension.txt @@ -19,7 +19,7 @@ namespace _ReswPlus_AutoGenerated.Plurals if (_pluralProvider is null) { CreatePluralProvider(); - if (_pluralProvider == null) + if (_pluralProvider is null) { return ""; } diff --git a/tests/ReswPlusUnitTests/ReswPlusUnitTests.csproj b/tests/ReswPlusUnitTests/ReswPlusUnitTests.csproj index 1103a97..adb26bc 100644 --- a/tests/ReswPlusUnitTests/ReswPlusUnitTests.csproj +++ b/tests/ReswPlusUnitTests/ReswPlusUnitTests.csproj @@ -1,8 +1,7 @@ - net6.0-windows - + net9.0-windows10.0.19041.0 false @@ -16,7 +15,10 @@ - + + {44d125b3-6c8a-4f2f-a76f-bffb2ad7e70d} + ReswPlus.SourceGenerator +