diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs index 8c24eda9..845149bd 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs @@ -1,8 +1,7 @@ -#nullable disable - // -using Microsoft.CodeAnalysis; +// ReSharper disable All -- generated code using System; +using Microsoft.CodeAnalysis; namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs index 68a892a9..4bda70ff 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs @@ -1,14 +1,14 @@ -#nullable disable - // -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; +// ReSharper disable All -- generated code + using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { @@ -61,7 +61,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework var diagnostics = new List(); foreach (Project project in projects) { - CompilationWithAnalyzers compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); + CompilationWithAnalyzers compilationWithAnalyzers = project.GetCompilationAsync().Result!.WithAnalyzers(ImmutableArray.Create(analyzer)); var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; foreach (Diagnostic diag in diags) { @@ -74,7 +74,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework for (int i = 0; i < documents.Length; i++) { Document document = documents[i]; - SyntaxTree tree = document.GetSyntaxTreeAsync().Result; + SyntaxTree? tree = document.GetSyntaxTreeAsync().Result; if (tree == diag.Location.SourceTree) { diagnostics.Add(diag); @@ -126,17 +126,6 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework return documents; } - /// - /// Create a Document from a string through creating a project that contains it. - /// - /// Classes in the form of a string - /// The language the source code is in - /// A Document created from the source string - protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) - { - return CreateProject(new[] { source }, language).Documents.First(); - } - /// /// Create a project using the inputted strings as sources. /// @@ -167,7 +156,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); count++; } - return solution.GetProject(projectId); + return solution.GetProject(projectId)!; } #endregion } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs index 4170042d..efe69e4a 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs @@ -1,6 +1,6 @@ -#nullable disable - // +// ReSharper disable All -- generated code + using System.Collections.Generic; using System.Linq; using System.Text; @@ -19,18 +19,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// /// Get the CSharp analyzer being tested - to be implemented in non-abstract class /// - protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - { - return null; - } - - /// - /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class - /// - protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() - { - return null; - } + protected abstract DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer(); #endregion #region Verifier wrappers @@ -46,17 +35,6 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework this.VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); } - /// - /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected - /// - /// An array of strings to create source documents from to run the analyzers on - /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) - { - this.VerifyDiagnostics(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); - } - /// /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, /// then verifies each of them. @@ -222,11 +200,10 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework Assert.IsTrue(location.IsInSource, $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, + "GetCSharpResultAt", linePosition.Line + 1, linePosition.Character + 1, analyzerType.Name, diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs index 54aa1c6c..8bedd583 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs @@ -1,12 +1,8 @@ -#nullable disable - // ReSharper disable CheckNamespace -- matches Stardew Valley's code -using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; namespace Netcode { /// A simplified version of Stardew Valley's Netcode.NetCollection for unit testing. - public class NetCollection : Collection, IList, ICollection, IEnumerable, IEnumerable { } + public class NetCollection : Collection { } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs index 1c349a0b..8f6b8987 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs @@ -1,5 +1,3 @@ -#nullable disable - // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { @@ -9,10 +7,13 @@ namespace Netcode public class NetFieldBase where TSelf : NetFieldBase { /// The synchronised value. - public T Value { get; set; } + public T? Value { get; set; } /// Implicitly convert a net field to the its type. /// The field to convert. - public static implicit operator T(NetFieldBase field) => field.Value; + public static implicit operator T?(NetFieldBase field) + { + return field.Value; + } } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs index e8e1dc63..b3abc467 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs @@ -1,5 +1,3 @@ -#nullable disable - // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs index f7fb9617..33e616fb 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs @@ -1,11 +1,8 @@ -#nullable disable - // ReSharper disable CheckNamespace -- matches Stardew Valley's code -using System.Collections; using System.Collections.Generic; namespace Netcode { /// A simplified version of Stardew Valley's Netcode.NetObjectList for unit testing. - public class NetList : List, IList, ICollection, IEnumerable, IEnumerable { } + public class NetList : List { } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs index 74c17843..7814e7d6 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs @@ -1,5 +1,3 @@ -#nullable disable - // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs index bdbf9b45..dbd05792 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs @@ -1,7 +1,5 @@ -#nullable disable - // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code -#pragma warning disable 649 // (never assigned) -- only used to test type conversions +// ReSharper disable UnusedMember.Global -- used dynamically for unit tests using System.Collections.Generic; namespace StardewValley @@ -10,6 +8,6 @@ namespace StardewValley internal class Farmer { /// A sample field which should be replaced with a different property. - public readonly IDictionary friendships; + public readonly IDictionary friendships = new Dictionary(); } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs index d1f0afc4..d50deb72 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs @@ -1,6 +1,5 @@ -#nullable disable - // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code +// ReSharper disable UnusedMember.Global -- used dynamically for unit tests using Netcode; namespace StardewValley diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs index f54b22fe..151010a7 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs @@ -1,5 +1,3 @@ -#nullable disable - // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code using Netcode; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs index 29f3b956..f11a59d3 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs index 1cf7369f..76607b8e 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; diff --git a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs index 285dd259..ac7bd338 100644 --- a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs +++ b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using SMAPI.Tests.ModApiConsumer.Interfaces; diff --git a/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs b/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs index 23491fd1..7f94e137 100644 --- a/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs +++ b/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Reflection; diff --git a/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs b/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs index b5870baa..77001e4c 100644 --- a/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs +++ b/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace SMAPI.Tests.ModApiProvider.Framework { /// The base class for . @@ -9,6 +7,6 @@ namespace SMAPI.Tests.ModApiProvider.Framework ** Test interface *********/ /// A property inherited from a base class. - public string InheritedProperty { get; set; } + public string? InheritedProperty { get; set; } } } diff --git a/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs b/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs index 82e902f5..e7e1ccef 100644 --- a/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs +++ b/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs @@ -1,4 +1,4 @@ -#nullable disable +// ReSharper disable UnusedMember.Global -- used dynamically through proxies using System; using System.Collections.Generic; @@ -16,7 +16,7 @@ namespace SMAPI.Tests.ModApiProvider.Framework ** Events ****/ /// A simple event field. - public event EventHandler OnEventRaised; + public event EventHandler? OnEventRaised; /// A simple event property with custom add/remove logic. public event EventHandler OnEventRaisedProperty @@ -33,16 +33,16 @@ namespace SMAPI.Tests.ModApiProvider.Framework public int NumberProperty { get; set; } /// A simple object property. - public object ObjectProperty { get; set; } + public object? ObjectProperty { get; set; } /// A simple list property. - public List ListProperty { get; set; } + public List? ListProperty { get; set; } /// A simple list property with an interface. - public IList ListPropertyWithInterface { get; set; } + public IList? ListPropertyWithInterface { get; set; } /// A property with nested generics. - public IDictionary> GenericsProperty { get; set; } + public IDictionary>? GenericsProperty { get; set; } /// A property using an enum available to both mods. public BindingFlags EnumProperty { get; set; } diff --git a/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs b/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs index 3fc8d749..c36e1c6d 100644 --- a/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs +++ b/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Reflection; using SMAPI.Tests.ModApiProvider.Framework; diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index ef8a08ef..8018442c 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using FluentAssertions; @@ -28,7 +26,7 @@ namespace SMAPI.Tests.Core [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)] [TestCase("Characters/Dialogue\\Abigail.fr-FR", "Characters/Dialogue/Abigail.fr-FR", null, null)] [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)] - public void Constructor_Valid(string name, string expectedBaseName, string expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode) + public void Constructor_Valid(string name, string expectedBaseName, string? expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode) { // arrange name = PathUtilities.NormalizeAssetName(name); @@ -55,13 +53,13 @@ namespace SMAPI.Tests.Core [TestCase(" ")] [TestCase("\t")] [TestCase(" \t ")] - public void Constructor_NullOrWhitespace(string name) + public void Constructor_NullOrWhitespace(string? name) { // act - ArgumentException exception = Assert.Throws(() => _ = AssetName.Parse(name, null)); + ArgumentException exception = Assert.Throws(() => _ = AssetName.Parse(name!, null))!; // assert - exception!.ParamName.Should().Be("rawName"); + exception.ParamName.Should().Be("rawName"); exception.Message.Should().Be("The asset name can't be null or empty. (Parameter 'rawName')"); } diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs index 1bf2ed68..0b4919ed 100644 --- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs +++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -41,7 +39,7 @@ namespace SMAPI.Tests.Core public void CanProxy_EventField() { // arrange - var providerMod = new ProviderMod(); + ProviderMod providerMod = new(); object implementation = providerMod.GetModApi(); int expectedValue = this.Random.Next(); @@ -61,7 +59,7 @@ namespace SMAPI.Tests.Core public void CanProxy_EventProperty() { // arrange - var providerMod = new ProviderMod(); + ProviderMod providerMod = new(); object implementation = providerMod.GetModApi(); int expectedValue = this.Random.Next(); @@ -86,7 +84,7 @@ namespace SMAPI.Tests.Core public void CanProxy_Properties(string setVia) { // arrange - var providerMod = new ProviderMod(); + ProviderMod providerMod = new(); object implementation = providerMod.GetModApi(); int expectedNumber = this.Random.Next(); int expectedObject = this.Random.Next(); @@ -317,13 +315,13 @@ namespace SMAPI.Tests.Core /// Get a property value from an instance. /// The instance whose property to read. /// The property name. - private object GetPropertyValue(object parent, string name) + private object? GetPropertyValue(object parent, string name) { if (parent is null) throw new ArgumentNullException(nameof(parent)); Type type = parent.GetType(); - PropertyInfo property = type.GetProperty(name); + PropertyInfo? property = type.GetProperty(name); if (property is null) throw new InvalidOperationException($"The '{type.FullName}' type has no public property named '{name}'."); diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index b8649a7d..bd621bbf 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -507,7 +507,7 @@ namespace SMAPI.Tests.Core /// Whether the code being tested is allowed to change the mod status. private Mock GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false) { - IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray()); + IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray()); return this.GetMetadata(manifest, allowStatusChange); } diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs index f8f0e315..a65bf772 100644 --- a/src/SMAPI.Tests/Core/TranslationTests.cs +++ b/src/SMAPI.Tests/Core/TranslationTests.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NUnit.Framework; using StardewModdingAPI; @@ -18,7 +17,7 @@ namespace SMAPI.Tests.Core ** Data *********/ /// Sample translation text for unit tests. - public static string[] Samples = { null, "", " ", "boop", " boop " }; + public static string?[] Samples = { null, "", " ", "boop", " boop " }; /********* @@ -36,13 +35,13 @@ namespace SMAPI.Tests.Core // act ITranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); Translation translation = helper.Get("key"); - Translation[] translationList = helper.GetTranslations()?.ToArray(); + Translation[]? translationList = helper.GetTranslations()?.ToArray(); // assert Assert.AreEqual("en", helper.Locale, "The locale doesn't match the input value."); Assert.AreEqual(LocalizedContentManager.LanguageCode.en, helper.LocaleEnum, "The locale enum doesn't match the input value."); Assert.IsNotNull(translationList, "The full list of translations is unexpectedly null."); - Assert.AreEqual(0, translationList.Length, "The full list of translations is unexpectedly not empty."); + Assert.AreEqual(0, translationList!.Length, "The full list of translations is unexpectedly not empty."); Assert.IsNotNull(translation, "The translation helper unexpectedly returned a null translation."); Assert.AreEqual(this.GetPlaceholderText("key"), translation.ToString(), "The translation returned an unexpected value."); @@ -56,7 +55,7 @@ namespace SMAPI.Tests.Core var expected = this.GetExpectedTranslations(); // act - var actual = new Dictionary(); + var actual = new Dictionary(); TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { @@ -109,13 +108,13 @@ namespace SMAPI.Tests.Core [TestCase(" ", ExpectedResult = true)] [TestCase("boop", ExpectedResult = true)] [TestCase(" boop ", ExpectedResult = true)] - public bool Translation_HasValue(string text) + public bool Translation_HasValue(string? text) { return new Translation("pt-BR", "key", text).HasValue(); } [Test(Description = "Assert that the translation's ToString method returns the expected text for various inputs.")] - public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string text) + public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string? text) { // act Translation translation = new("pt-BR", "key", text); @@ -128,7 +127,7 @@ namespace SMAPI.Tests.Core } [Test(Description = "Assert that the translation's implicit string conversion returns the expected text for various inputs.")] - public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string text) + public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string? text) { // act Translation translation = new("pt-BR", "key", text); @@ -141,7 +140,7 @@ namespace SMAPI.Tests.Core } [Test(Description = "Assert that the translation returns the expected text given a use-placeholder setting.")] - public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string text) + public void Translation_UsePlaceholder([Values(true, false)] bool value, [ValueSource(nameof(TranslationTests.Samples))] string? text) { // act Translation translation = new Translation("pt-BR", "key", text).UsePlaceholder(value); @@ -156,7 +155,7 @@ namespace SMAPI.Tests.Core } [Test(Description = "Assert that the translation returns the expected text after setting the default.")] - public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string text, [ValueSource(nameof(TranslationTests.Samples))] string @default) + public void Translation_Default([ValueSource(nameof(TranslationTests.Samples))] string? text, [ValueSource(nameof(TranslationTests.Samples))] string? @default) { // act Translation translation = new Translation("pt-BR", "key", text).Default(@default); @@ -192,7 +191,7 @@ namespace SMAPI.Tests.Core break; case "class": - translation = translation.Tokens(new TokenModel { Start = start, Middle = middle, End = end }); + translation = translation.Tokens(new TokenModel(start, middle, end)); break; case "IDictionary": @@ -331,16 +330,36 @@ namespace SMAPI.Tests.Core ** Test models *********/ /// A model used to test token support. + [SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Used dynamically via translation helper.")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local", Justification = "Used dynamically via translation helper.")] private class TokenModel { + /********* + ** Accessors + *********/ /// A sample token property. - public string Start { get; set; } + public string Start { get; } /// A sample token property. - public string Middle { get; set; } + public string Middle { get; } /// A sample token field. public string End; + + + /********* + ** public methods + *********/ + /// Construct an instance. + /// A sample token property. + /// A sample token field. + /// A sample token property. + public TokenModel(string start, string middle, string end) + { + this.Start = start; + this.Middle = middle; + this.End = end; + } } } } diff --git a/src/SMAPI.Tests/Sample.cs b/src/SMAPI.Tests/Sample.cs index 6d4339ca..9587a100 100644 --- a/src/SMAPI.Tests/Sample.cs +++ b/src/SMAPI.Tests/Sample.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace SMAPI.Tests diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs index f5c156c4..060c93ed 100644 --- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs +++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using NUnit.Framework; @@ -46,7 +44,7 @@ namespace SMAPI.Tests.Utilities [TestCase(",", ExpectedResult = "None")] [TestCase("A,", ExpectedResult = "A")] [TestCase(",A", ExpectedResult = "A")] - public string TryParse_MultiValues(string input) + public string TryParse_MultiValues(string? input) { // act bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); @@ -100,13 +98,15 @@ namespace SMAPI.Tests.Utilities public SButtonState GetState(string input, string stateMap) { // act - bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors); if (success && parsed?.Keybinds != null) { - foreach (var keybind in parsed.Keybinds) + foreach (Keybind? keybind in parsed.Keybinds) + { #pragma warning disable 618 // method is marked obsolete because it should only be used in unit tests keybind.GetButtonState = key => this.GetStateFromMap(key, stateMap); #pragma warning restore 618 + } } // assert @@ -114,7 +114,7 @@ namespace SMAPI.Tests.Utilities Assert.IsNotNull(parsed, "The parsed result should not be null."); Assert.IsNotNull(errors, message: "The errors should never be null."); Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors."); - return parsed.GetState(); + return parsed!.GetState(); } diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs index ae2cc6ce..3219d89d 100644 --- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs +++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.IO; using NUnit.Framework; using StardewModdingAPI.Toolkit.Utilities; @@ -8,6 +7,7 @@ namespace SMAPI.Tests.Utilities { /// Unit tests for . [TestFixture] + [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These are standard game install paths.")] internal class PathUtilitiesTests { /********* @@ -16,136 +16,125 @@ namespace SMAPI.Tests.Utilities /// Sample paths used in unit tests. public static readonly SamplePath[] SamplePaths = { // Windows absolute path - new() - { - OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", + new( + OriginalPath: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", - Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, - SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley" }, + Segments: new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3: new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley" }, - NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", - NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley" - }, + NormalizedOnWindows: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", + NormalizedOnUnix: @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley" + ), // Windows absolute path (with trailing slash) - new() - { - OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", + new( + OriginalPath: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", - Segments = new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, - SegmentsLimit3 = new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley\" }, + Segments: new[] { "C:", "Program Files (x86)", "Steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3: new [] { "C:", "Program Files (x86)", @"Steam\steamapps\common\Stardew Valley\" }, - NormalizedOnWindows = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", - NormalizedOnUnix = @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley/" - }, + NormalizedOnWindows: @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", + NormalizedOnUnix: @"C:/Program Files (x86)/Steam/steamapps/common/Stardew Valley/" + ), // Windows relative path - new() - { - OriginalPath = @"Content\Characters\Dialogue\Abigail", + new( + OriginalPath: @"Content\Characters\Dialogue\Abigail", - Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" }, - SegmentsLimit3 = new [] { "Content", "Characters", @"Dialogue\Abigail" }, + Segments: new [] { "Content", "Characters", "Dialogue", "Abigail" }, + SegmentsLimit3: new [] { "Content", "Characters", @"Dialogue\Abigail" }, - NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail", - NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail" - }, + NormalizedOnWindows: @"Content\Characters\Dialogue\Abigail", + NormalizedOnUnix: @"Content/Characters/Dialogue/Abigail" + ), // Windows relative path (with directory climbing) - new() - { - OriginalPath = @"..\..\Content", + new( + OriginalPath: @"..\..\Content", - Segments = new [] { "..", "..", "Content" }, - SegmentsLimit3 = new [] { "..", "..", "Content" }, + Segments: new [] { "..", "..", "Content" }, + SegmentsLimit3: new [] { "..", "..", "Content" }, - NormalizedOnWindows = @"..\..\Content", - NormalizedOnUnix = @"../../Content" - }, + NormalizedOnWindows: @"..\..\Content", + NormalizedOnUnix: @"../../Content" + ), // Windows UNC path - new() - { - OriginalPath = @"\\unc\path", + new( + OriginalPath: @"\\unc\path", - Segments = new [] { "unc", "path" }, - SegmentsLimit3 = new [] { "unc", "path" }, + Segments: new [] { "unc", "path" }, + SegmentsLimit3: new [] { "unc", "path" }, - NormalizedOnWindows = @"\\unc\path", - NormalizedOnUnix = "/unc/path" // there's no good way to normalize this on Unix since UNC paths aren't supported; path normalization is meant for asset names anyway, so this test only ensures it returns some sort of sane value - }, + NormalizedOnWindows: @"\\unc\path", + NormalizedOnUnix: "/unc/path" // there's no good way to normalize this on Unix since UNC paths aren't supported; path normalization is meant for asset names anyway, so this test only ensures it returns some sort of sane value + ), // Linux absolute path - new() - { - OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley", + new( + OriginalPath: @"/home/.steam/steam/steamapps/common/Stardew Valley", - Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, - SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley" }, + Segments: new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3: new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley" }, - NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley", - NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley" - }, + NormalizedOnWindows: @"\home\.steam\steam\steamapps\common\Stardew Valley", + NormalizedOnUnix: @"/home/.steam/steam/steamapps/common/Stardew Valley" + ), // Linux absolute path (with trailing slash) - new() - { - OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley/", + new( + OriginalPath: @"/home/.steam/steam/steamapps/common/Stardew Valley/", - Segments = new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, - SegmentsLimit3 = new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley/" }, + Segments: new [] { "home", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3: new [] { "home", ".steam", "steam/steamapps/common/Stardew Valley/" }, - NormalizedOnWindows = @"\home\.steam\steam\steamapps\common\Stardew Valley\", - NormalizedOnUnix = @"/home/.steam/steam/steamapps/common/Stardew Valley/" - }, + NormalizedOnWindows: @"\home\.steam\steam\steamapps\common\Stardew Valley\", + NormalizedOnUnix: @"/home/.steam/steam/steamapps/common/Stardew Valley/" + ), // Linux absolute path (with ~) - new() - { - OriginalPath = @"~/.steam/steam/steamapps/common/Stardew Valley", + new( + OriginalPath: @"~/.steam/steam/steamapps/common/Stardew Valley", - Segments = new [] { "~", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, - SegmentsLimit3 = new [] { "~", ".steam", "steam/steamapps/common/Stardew Valley" }, + Segments: new [] { "~", ".steam", "steam", "steamapps", "common", "Stardew Valley" }, + SegmentsLimit3: new [] { "~", ".steam", "steam/steamapps/common/Stardew Valley" }, - NormalizedOnWindows = @"~\.steam\steam\steamapps\common\Stardew Valley", - NormalizedOnUnix = @"~/.steam/steam/steamapps/common/Stardew Valley" - }, + NormalizedOnWindows: @"~\.steam\steam\steamapps\common\Stardew Valley", + NormalizedOnUnix: @"~/.steam/steam/steamapps/common/Stardew Valley" + ), // Linux relative path - new() - { - OriginalPath = @"Content/Characters/Dialogue/Abigail", + new( + OriginalPath: @"Content/Characters/Dialogue/Abigail", - Segments = new [] { "Content", "Characters", "Dialogue", "Abigail" }, - SegmentsLimit3 = new [] { "Content", "Characters", "Dialogue/Abigail" }, + Segments: new [] { "Content", "Characters", "Dialogue", "Abigail" }, + SegmentsLimit3: new [] { "Content", "Characters", "Dialogue/Abigail" }, - NormalizedOnWindows = @"Content\Characters\Dialogue\Abigail", - NormalizedOnUnix = @"Content/Characters/Dialogue/Abigail" - }, + NormalizedOnWindows: @"Content\Characters\Dialogue\Abigail", + NormalizedOnUnix: @"Content/Characters/Dialogue/Abigail" + ), // Linux relative path (with directory climbing) - new() - { - OriginalPath = @"../../Content", + new( + OriginalPath: @"../../Content", - Segments = new [] { "..", "..", "Content" }, - SegmentsLimit3 = new [] { "..", "..", "Content" }, + Segments: new [] { "..", "..", "Content" }, + SegmentsLimit3: new [] { "..", "..", "Content" }, - NormalizedOnWindows = @"..\..\Content", - NormalizedOnUnix = @"../../Content" - }, + NormalizedOnWindows: @"..\..\Content", + NormalizedOnUnix: @"../../Content" + ), // Mixed directory separators - new() - { - OriginalPath = @"C:\some/mixed\path/separators", + new( + OriginalPath: @"C:\some/mixed\path/separators", - Segments = new [] { "C:", "some", "mixed", "path", "separators" }, - SegmentsLimit3 = new [] { "C:", "some", @"mixed\path/separators" }, + Segments: new [] { "C:", "some", "mixed", "path", "separators" }, + SegmentsLimit3: new [] { "C:", "some", @"mixed\path/separators" }, - NormalizedOnWindows = @"C:\some\mixed\path\separators", - NormalizedOnUnix = @"C:/some/mixed/path/separators" - }, + NormalizedOnWindows: @"C:\some\mixed\path\separators", + NormalizedOnUnix: @"C:/some/mixed/path/separators" + ) }; @@ -283,14 +272,14 @@ namespace SMAPI.Tests.Utilities /********* ** Private classes *********/ - public class SamplePath + /// A sample path in multiple formats. + /// The original path to pass to the . + /// The normalized path segments. + /// The normalized path segments, if we stop segmenting after the second one. + /// The normalized form on Windows. + /// The normalized form on Linux or macOS. + public record SamplePath(string OriginalPath, string[] Segments, string[] SegmentsLimit3, string NormalizedOnWindows, string NormalizedOnUnix) { - public string OriginalPath { get; set; } - public string[] Segments { get; set; } - public string[] SegmentsLimit3 { get; set; } - public string NormalizedOnWindows { get; set; } - public string NormalizedOnUnix { get; set; } - public override string ToString() { return this.OriginalPath; diff --git a/src/SMAPI.Tests/Utilities/SDateTests.cs b/src/SMAPI.Tests/Utilities/SDateTests.cs index a4a36828..b9c3d202 100644 --- a/src/SMAPI.Tests/Utilities/SDateTests.cs +++ b/src/SMAPI.Tests/Utilities/SDateTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -258,7 +256,7 @@ namespace SMAPI.Tests.Utilities { SDate date = new(day, season, year); int hash = date.GetHashCode(); - if (hashes.TryGetValue(hash, out SDate otherDate)) + if (hashes.TryGetValue(hash, out SDate? otherDate)) Assert.Fail($"Received identical hash code {hash} for dates {otherDate} and {date}."); if (hash < lastHash) Assert.Fail($"Received smaller hash code for date {date} ({hash}) relative to {hashes[lastHash]} ({lastHash})."); @@ -298,7 +296,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)] - public bool Operators_Equals(string now, string other) + public bool Operators_Equals(string? now, string other) { return this.GetDate(now) == this.GetDate(other); } @@ -312,7 +310,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)] - public bool Operators_NotEquals(string now, string other) + public bool Operators_NotEquals(string? now, string other) { return this.GetDate(now) != this.GetDate(other); } @@ -326,7 +324,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)] - public bool Operators_LessThan(string now, string other) + public bool Operators_LessThan(string? now, string other) { return this.GetDate(now) < this.GetDate(other); } @@ -340,7 +338,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = true)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = true)] - public bool Operators_LessThanOrEqual(string now, string other) + public bool Operators_LessThanOrEqual(string? now, string other) { return this.GetDate(now) <= this.GetDate(other); } @@ -354,7 +352,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)] - public bool Operators_MoreThan(string now, string other) + public bool Operators_MoreThan(string? now, string other) { return this.GetDate(now) > this.GetDate(other); } @@ -368,7 +366,7 @@ namespace SMAPI.Tests.Utilities [TestCase(Dates.Now, Dates.NextDay, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextMonth, ExpectedResult = false)] [TestCase(Dates.Now, Dates.NextYear, ExpectedResult = false)] - public bool Operators_MoreThanOrEqual(string now, string other) + public bool Operators_MoreThanOrEqual(string? now, string other) { return this.GetDate(now) > this.GetDate(other); } @@ -379,7 +377,8 @@ namespace SMAPI.Tests.Utilities *********/ /// Convert a string date into a game date, to make unit tests easier to read. /// The date string like "dd MMMM yy". - private SDate GetDate(string dateStr) + [return: NotNullIfNotNull("dateStr")] + private SDate? GetDate(string? dateStr) { if (dateStr == null) return null; diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs index 7695fbf8..8e7e1fb8 100644 --- a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs +++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using StardewModdingAPI; @@ -22,15 +20,14 @@ namespace SMAPI.Tests.WikiClient { // arrange string rawDescriptor = "-Nexus:2400, -B, XX → YY, Nexus:451,+A, XXX → YYY, invalidA →, → invalidB"; - string[] expectedAdd = new[] { "Nexus:451", "A" }; - string[] expectedRemove = new[] { "Nexus:2400", "B" }; + string[] expectedAdd = { "Nexus:451", "A" }; + string[] expectedRemove = { "Nexus:2400", "B" }; IDictionary expectedReplace = new Dictionary { ["XX"] = "YY", ["XXX"] = "YYY" }; - string[] expectedErrors = new[] - { + string[] expectedErrors = { "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.", "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value." }; @@ -50,15 +47,14 @@ namespace SMAPI.Tests.WikiClient { // arrange string rawDescriptor = "-1.0.1, -2.0-beta, 1.00 → 1.0, 1.0.0,+2.0-beta.15, 2.0 → 2.0-beta, invalidA →, → invalidB"; - string[] expectedAdd = new[] { "1.0.0", "2.0.0-beta.15" }; - string[] expectedRemove = new[] { "1.0.1", "2.0.0-beta" }; + string[] expectedAdd = { "1.0.0", "2.0.0-beta.15" }; + string[] expectedRemove = { "1.0.1", "2.0.0-beta" }; IDictionary expectedReplace = new Dictionary { ["1.00"] = "1.0.0", ["2.0.0"] = "2.0.0-beta" }; - string[] expectedErrors = new[] - { + string[] expectedErrors = { "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.", "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value." }; @@ -67,7 +63,7 @@ namespace SMAPI.Tests.WikiClient ChangeDescriptor parsed = ChangeDescriptor.Parse( rawDescriptor, out string[] errors, - formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion version) + formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion? version) ? version.ToString() : raw ); @@ -111,9 +107,9 @@ namespace SMAPI.Tests.WikiClient [TestCase("", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:A, Nexus:B")] [TestCase("Nexus:2400", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:A, Nexus:B")] [TestCase("Nexus:2400, Nexus:2401, Nexus:B,Chucklefish:14", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:2401, Nexus:B, Nexus:A")] - public string Apply_Raw(string input, string descriptor) + public string Apply_Raw(string input, string? descriptor) { - var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors); + ChangeDescriptor parsed = ChangeDescriptor.Parse(descriptor, out string[] errors); Assert.IsEmpty(errors, "Parsing the descriptor failed."); @@ -128,7 +124,7 @@ namespace SMAPI.Tests.WikiClient [TestCase("-Nexus:2400", ExpectedResult = "-Nexus:2400")] [TestCase(" Nexus:2400 →Nexus:2401 ", ExpectedResult = "Nexus:2400 → Nexus:2401")] [TestCase("+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "+Nexus:A, +Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A → Nexus:B")] - public string ToString(string descriptor) + public string ToString(string? descriptor) { var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors); diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs index 5978803e..8646f1cc 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki @@ -47,6 +48,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// Apply the change descriptors to a comma-delimited field. /// The raw field text. /// Returns the modified field. +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("rawField")] +#endif public string? ApplyToCopy(string? rawField) { // get list @@ -119,7 +123,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki /// The raw change descriptor. /// The human-readable error message describing any invalid values that were ignored. /// Format a raw value into a normalized form if needed. - public static ChangeDescriptor Parse(string descriptor, out string[] errors, Func? formatValue = null) + public static ChangeDescriptor Parse(string? descriptor, out string[] errors, Func? formatValue = null) { // init formatValue ??= p => p;