diff --git a/src/SMAPI.Toolkit/Framework/Constants.cs b/src/SMAPI.Toolkit/Framework/Constants.cs
index c3a787c7..55f26582 100644
--- a/src/SMAPI.Toolkit/Framework/Constants.cs
+++ b/src/SMAPI.Toolkit/Framework/Constants.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
namespace StardewModdingAPI.Toolkit.Framework
{
/// Contains the SMAPI installer's constants and assumptions.
diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
index ac6fe411..4f872f1c 100644
--- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -41,7 +39,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
IEnumerable paths = this
.GetCustomInstallPaths()
.Concat(this.GetDefaultInstallPaths())
- .Select(PathUtilities.NormalizePath)
+ .Select(path => PathUtilities.NormalizePath(path))
.Distinct(StringComparer.OrdinalIgnoreCase);
// yield valid folders
@@ -80,10 +78,12 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
return GameFolderType.NoGameFound;
// get assembly version
- Version version;
+ Version? version;
try
{
version = AssemblyName.GetAssemblyName(executable.FullName).Version;
+ if (version == null)
+ return GameFolderType.InvalidUnknown;
}
catch
{
@@ -123,7 +123,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
case Platform.Linux:
case Platform.Mac:
{
- string home = Environment.GetEnvironmentVariable("HOME");
+ string home = Environment.GetEnvironmentVariable("HOME")!;
// Linux
yield return $"{home}/GOG Games/Stardew Valley/game";
@@ -148,13 +148,13 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
};
foreach (var pair in registryKeys)
{
- string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value);
+ string? path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value);
if (!string.IsNullOrWhiteSpace(path))
yield return path;
}
// via Steam library path
- string steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath");
+ string? steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath");
if (steamPath != null)
yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley");
#endif
@@ -188,7 +188,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
private IEnumerable GetCustomInstallPaths()
{
// get home path
- string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME");
+ string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME")!;
if (string.IsNullOrWhiteSpace(homePath))
yield break;
@@ -210,7 +210,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
}
// get install path
- XElement element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace
+ XElement? element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace
if (!string.IsNullOrWhiteSpace(element?.Value))
yield return element.Value.Trim();
}
@@ -219,27 +219,27 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
/// Get the value of a key in the Windows HKLM registry.
/// The full path of the registry key relative to HKLM.
/// The name of the value.
- private string GetLocalMachineRegistryValue(string key, string name)
+ private string? GetLocalMachineRegistryValue(string key, string name)
{
RegistryKey localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine;
- RegistryKey openKey = localMachine.OpenSubKey(key);
+ RegistryKey? openKey = localMachine.OpenSubKey(key);
if (openKey == null)
return null;
using (openKey)
- return (string)openKey.GetValue(name);
+ return (string?)openKey.GetValue(name);
}
/// Get the value of a key in the Windows HKCU registry.
/// The full path of the registry key relative to HKCU.
/// The name of the value.
- private string GetCurrentUserRegistryValue(string key, string name)
+ private string? GetCurrentUserRegistryValue(string key, string name)
{
- RegistryKey currentuser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser;
- RegistryKey openKey = currentuser.OpenSubKey(key);
+ RegistryKey currentUser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser;
+ RegistryKey? openKey = currentUser.OpenSubKey(key);
if (openKey == null)
return null;
using (openKey)
- return (string)openKey.GetValue(name);
+ return (string?)openKey.GetValue(name);
}
#endif
}
diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
index 9998edec..6978567e 100644
--- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
+++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -59,11 +57,13 @@ namespace StardewModdingAPI.Toolkit.Framework
#if SMAPI_FOR_WINDOWS
try
{
- return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem")
+ string? result = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem")
.Get()
.Cast()
.Select(entry => entry.GetPropertyValue("Caption").ToString())
.FirstOrDefault();
+
+ return result ?? "Windows";
}
catch { }
#endif
@@ -137,7 +137,7 @@ namespace StardewModdingAPI.Toolkit.Framework
buffer = Marshal.AllocHGlobal(8192);
if (LowLevelEnvironmentUtility.uname(buffer) == 0)
{
- string os = Marshal.PtrToStringAnsi(buffer);
+ string? os = Marshal.PtrToStringAnsi(buffer);
return os == "Darwin";
}
return false;
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
index 81d72c0b..da2a3c85 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -24,13 +22,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModType Type { get; }
/// The mod manifest.
- public Manifest Manifest { get; }
+ public Manifest? Manifest { get; }
/// The error which occurred parsing the manifest, if any.
public ModParseError ManifestParseError { get; set; }
/// A human-readable message for the , if any.
- public string ManifestParseErrorText { get; set; }
+ public string? ManifestParseErrorText { get; set; }
/*********
@@ -51,7 +49,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// The mod manifest.
/// The error which occurred parsing the manifest, if any.
/// A human-readable message for the , if any.
- public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, ModParseError manifestParseError, string manifestParseErrorText)
+ public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText)
{
// save info
this.Directory = directory;
@@ -61,9 +59,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
this.ManifestParseErrorText = manifestParseErrorText;
// set display name
- this.DisplayName = manifest?.Name;
- if (string.IsNullOrWhiteSpace(this.DisplayName))
- this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName);
+ this.DisplayName = !string.IsNullOrWhiteSpace(manifest?.Name)
+ ? manifest.Name
+ : PathUtilities.GetRelativePath(root.FullName, directory.FullName);
}
/// Get the update keys for a mod.
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
index 621f1e28..2af30092 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.IO;
@@ -117,7 +115,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder)
{
// find manifest.json
- FileInfo manifestFile = this.FindManifest(searchFolder);
+ FileInfo? manifestFile = this.FindManifest(searchFolder);
// set appropriate invalid-mod error
if (manifestFile == null)
@@ -147,13 +145,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
}
// read mod info
- Manifest manifest = null;
+ Manifest? manifest = null;
ModParseError error = ModParseError.None;
- string errorText = null;
+ string? errorText = null;
{
try
{
- if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest) || manifest == null)
+ if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest))
{
error = ModParseError.ManifestInvalid;
errorText = "its manifest is invalid.";
@@ -186,7 +184,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
}
// build result
- return new ModFolder(root, manifestFile.Directory, type, manifest, error, errorText);
+ return new ModFolder(root, manifestFile.Directory!, type, manifest, error, errorText);
}
@@ -249,7 +247,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// Find the manifest for a mod folder.
/// The folder to search.
- private FileInfo FindManifest(DirectoryInfo folder)
+ private FileInfo? FindManifest(DirectoryInfo folder)
{
while (true)
{
diff --git a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs
index 57eea2f7..836b1134 100644
--- a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs
+++ b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs
@@ -1,4 +1,4 @@
-#nullable disable
+using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Toolkit.Framework
{
@@ -18,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework
/// An optional prerelease tag.
/// Optional build metadata. This is ignored when determining version precedence.
/// Returns whether the version was successfully parsed.
- public static bool TryParse(string versionStr, bool allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata)
+ public static bool TryParse(string? versionStr, bool allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string? prereleaseTag, out string? buildMetadata)
{
// init
major = 0;
@@ -105,7 +105,7 @@ namespace StardewModdingAPI.Toolkit.Framework
/// The raw characters to parse.
/// The index of the next character to read.
/// The parsed tag.
- private static bool TryParseTag(char[] raw, ref int index, out string tag)
+ private static bool TryParseTag(char[] raw, ref int index, [NotNullWhen(true)] out string? tag)
{
// read tag length
int length = 0;
diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
index ec94ed51..d40d8f2b 100644
--- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
+++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs
@@ -1,6 +1,5 @@
-#nullable disable
-
using System;
+using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Toolkit.Framework.UpdateData
{
@@ -17,10 +16,11 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
public ModSiteKey Site { get; }
/// The mod ID within the repository.
- public string ID { get; }
+ [MemberNotNullWhen(true, nameof(LooksValid))]
+ public string? ID { get; }
/// If specified, a substring in download names/descriptions to match.
- public string Subkey { get; }
+ public string? Subkey { get; }
/// Whether the update key seems to be valid.
public bool LooksValid { get; }
@@ -34,9 +34,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// The mod site containing the mod.
/// The mod ID within the site.
/// If specified, a substring in download names/descriptions to match.
- public UpdateKey(string rawText, ModSiteKey site, string id, string subkey)
+ public UpdateKey(string? rawText, ModSiteKey site, string? id, string? subkey)
{
- this.RawText = rawText?.Trim();
+ this.RawText = rawText?.Trim() ?? string.Empty;
this.Site = site;
this.ID = id?.Trim();
this.Subkey = subkey?.Trim();
@@ -49,19 +49,19 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// The mod site containing the mod.
/// The mod ID within the site.
/// If specified, a substring in download names/descriptions to match.
- public UpdateKey(ModSiteKey site, string id, string subkey)
+ public UpdateKey(ModSiteKey site, string? id, string? subkey)
: this(UpdateKey.GetString(site, id, subkey), site, id, subkey) { }
/// Parse a raw update key.
/// The raw update key to parse.
- public static UpdateKey Parse(string raw)
+ public static UpdateKey Parse(string? raw)
{
// extract site + ID
- string rawSite;
- string id;
+ string? rawSite;
+ string? id;
{
- string[] parts = raw?.Trim().Split(':');
- if (parts == null || parts.Length != 2)
+ string[]? parts = raw?.Trim().Split(':');
+ if (parts?.Length != 2)
return new UpdateKey(raw, ModSiteKey.Unknown, null, null);
rawSite = parts[0].Trim();
@@ -71,7 +71,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
id = null;
// extract subkey
- string subkey = null;
+ string? subkey = null;
if (id != null)
{
string[] parts = id.Split('@');
@@ -111,7 +111,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// Indicates whether the current object is equal to another object of the same type.
/// An object to compare with this object.
- public bool Equals(UpdateKey other)
+ public bool Equals(UpdateKey? other)
{
if (!this.LooksValid)
{
@@ -129,7 +129,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// Determines whether the specified object is equal to the current object.
/// The object to compare with the current object.
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
return obj is UpdateKey other && this.Equals(other);
}
@@ -145,7 +145,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// The mod site containing the mod.
/// The mod ID within the repository.
/// If specified, a substring in download names/descriptions to match.
- public static string GetString(ModSiteKey site, string id, string subkey = null)
+ public static string GetString(ModSiteKey site, string? id, string? subkey = null)
{
return $"{site}:{id}{subkey}".Trim();
}
diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs
index 9ae8cd1c..51f6fa24 100644
--- a/src/SMAPI.Toolkit/ModToolkit.cs
+++ b/src/SMAPI.Toolkit/ModToolkit.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -24,7 +22,7 @@ namespace StardewModdingAPI.Toolkit
private readonly string UserAgent;
/// Maps vendor keys (like Nexus) to their mod URL template (where {0} is the mod ID). This doesn't affect update checks, which defer to the remote web API.
- private readonly IDictionary VendorModUrls = new Dictionary()
+ private readonly Dictionary VendorModUrls = new()
{
[ModSiteKey.Chucklefish] = "https://community.playstarbound.com/resources/{0}",
[ModSiteKey.GitHub] = "https://github.com/{0}/releases",
@@ -45,7 +43,7 @@ namespace StardewModdingAPI.Toolkit
/// Construct an instance.
public ModToolkit()
{
- ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version);
+ ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version!);
this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}";
}
@@ -59,7 +57,7 @@ namespace StardewModdingAPI.Toolkit
/// Extract mod metadata from the wiki compatibility list.
public async Task GetWikiCompatibilityListAsync()
{
- var client = new WikiClient(this.UserAgent);
+ WikiClient client = new(this.UserAgent);
return await client.FetchModsAsync();
}
@@ -89,13 +87,13 @@ namespace StardewModdingAPI.Toolkit
/// Get an update URL for an update key (if valid).
/// The update key.
- public string GetUpdateUrl(string updateKey)
+ public string? GetUpdateUrl(string updateKey)
{
UpdateKey parsed = UpdateKey.Parse(updateKey);
if (!parsed.LooksValid)
return null;
- if (this.VendorModUrls.TryGetValue(parsed.Site, out string urlTemplate))
+ if (this.VendorModUrls.TryGetValue(parsed.Site, out string? urlTemplate))
return string.Format(urlTemplate, parsed.ID);
return null;
diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs
index ca9d15f5..cbdd7a85 100644
--- a/src/SMAPI.Toolkit/SemanticVersion.cs
+++ b/src/SMAPI.Toolkit/SemanticVersion.cs
@@ -91,7 +91,7 @@ namespace StardewModdingAPI.Toolkit
{
if (version == null)
throw new ArgumentNullException(nameof(version), "The input version string can't be null.");
- if (!SemanticVersionReader.TryParse(version, allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata) || (!allowNonStandard && platformRelease != 0))
+ if (!SemanticVersionReader.TryParse(version, allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string? prereleaseTag, out string? buildMetadata) || (!allowNonStandard && platformRelease != 0))
throw new FormatException($"The input '{version}' isn't a valid semantic version.");
this.MajorVersion = major;
diff --git a/src/SMAPI.Toolkit/SemanticVersionComparer.cs b/src/SMAPI.Toolkit/SemanticVersionComparer.cs
index a0472f62..85c974bd 100644
--- a/src/SMAPI.Toolkit/SemanticVersionComparer.cs
+++ b/src/SMAPI.Toolkit/SemanticVersionComparer.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Collections.Generic;
namespace StardewModdingAPI.Toolkit
@@ -18,7 +16,7 @@ namespace StardewModdingAPI.Toolkit
** Public methods
*********/
///
- public int Compare(ISemanticVersion x, ISemanticVersion y)
+ public int Compare(ISemanticVersion? x, ISemanticVersion? y)
{
if (object.ReferenceEquals(x, y))
return 0;
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
index d2dc0d22..faaeedea 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Models;
@@ -35,7 +33,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The object type.
/// The object being read.
/// The calling serializer.
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
return serializer.Deserialize(reader);
}
@@ -44,7 +42,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The JSON writer.
/// The value.
/// The calling serializer.
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
index 2c3c2ee7..c499a2c6 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
@@ -37,13 +35,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The object type.
/// The object being read.
/// The calling serializer.
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
List result = new List();
foreach (JObject obj in JArray.Load(reader).Children())
{
- string uniqueID = obj.ValueIgnoreCase(nameof(ManifestDependency.UniqueID));
- string minVersion = obj.ValueIgnoreCase(nameof(ManifestDependency.MinimumVersion));
+ string uniqueID = obj.ValueIgnoreCase(nameof(ManifestDependency.UniqueID))!; // will be validated separately if null
+ string? minVersion = obj.ValueIgnoreCase(nameof(ManifestDependency.MinimumVersion));
bool required = obj.ValueIgnoreCase(nameof(ManifestDependency.IsRequired)) ?? true;
result.Add(new ManifestDependency(uniqueID, minVersion, required));
}
@@ -54,7 +52,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The JSON writer.
/// The value.
/// The calling serializer.
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
index 9205cebe..c32c3185 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -41,15 +39,17 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The object type.
/// The object being read.
/// The calling serializer.
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
string path = reader.Path;
switch (reader.TokenType)
{
case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader));
+
case JsonToken.String:
return this.ReadString(JToken.Load(reader).Value(), path);
+
default:
throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path}).");
}
@@ -59,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// The JSON writer.
/// The value.
/// The calling serializer.
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value?.ToString());
}
@@ -75,7 +75,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
int major = obj.ValueIgnoreCase(nameof(ISemanticVersion.MajorVersion));
int minor = obj.ValueIgnoreCase(nameof(ISemanticVersion.MinorVersion));
int patch = obj.ValueIgnoreCase(nameof(ISemanticVersion.PatchVersion));
- string prereleaseTag = obj.ValueIgnoreCase(nameof(ISemanticVersion.PrereleaseTag));
+ string? prereleaseTag = obj.ValueIgnoreCase(nameof(ISemanticVersion.PrereleaseTag));
return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag);
}
@@ -83,11 +83,11 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// Read a JSON string.
/// The JSON string value.
/// The path to the current JSON node.
- private ISemanticVersion ReadString(string str, string path)
+ private ISemanticVersion? ReadString(string str, string path)
{
if (string.IsNullOrWhiteSpace(str))
return null;
- if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion version))
+ if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion? version))
throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path}).");
return version;
}
diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
index c923350c..1c59f5e7 100644
--- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
+++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -27,21 +25,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
}
- /// Writes the JSON representation of the object.
- /// The JSON writer.
- /// The value.
- /// The calling serializer.
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- throw new InvalidOperationException("This converter does not write JSON.");
- }
-
/// Reads the JSON representation of the object.
/// The JSON reader.
/// The object type.
/// The object being read.
/// The calling serializer.
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
string path = reader.Path;
switch (reader.TokenType)
@@ -60,6 +49,15 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
}
}
+ /// Writes the JSON representation of the object.
+ /// The JSON writer.
+ /// The value.
+ /// The calling serializer.
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ throw new InvalidOperationException("This converter does not write JSON.");
+ }
+
/*********
** Protected methods
diff --git a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
index 1fce5f9d..78297035 100644
--- a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
+++ b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Newtonsoft.Json.Linq;
@@ -12,12 +10,12 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// The value type.
/// The JSON object to search.
/// The field name.
- public static T ValueIgnoreCase(this JObject obj, string fieldName)
+ public static T? ValueIgnoreCase(this JObject obj, string fieldName)
{
- JToken token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
+ JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
return token != null
? token.Value()
- : default(T);
+ : default;
}
}
}
diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
index 9700e712..7d1804e5 100644
--- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
+++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs
@@ -1,7 +1,6 @@
-#nullable disable
-
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
@@ -38,7 +37,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// Returns false if the file doesn't exist, else true.
/// The given is empty or invalid.
/// The file contains invalid JSON.
- public bool ReadJsonFileIfExists(string fullPath, out TModel result)
+ public bool ReadJsonFileIfExists(string fullPath, [NotNullWhen(true)] out TModel? result)
{
// validate
if (string.IsNullOrWhiteSpace(fullPath))
@@ -52,7 +51,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
}
catch (Exception ex) when (ex is DirectoryNotFoundException or FileNotFoundException)
{
- result = default(TModel);
+ result = default;
return false;
}
@@ -60,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
try
{
result = this.Deserialize(json);
- return true;
+ return result != null;
}
catch (Exception ex)
{
@@ -90,7 +89,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath));
// create directory if needed
- string dir = Path.GetDirectoryName(fullPath);
+ string dir = Path.GetDirectoryName(fullPath)!;
if (dir == null)
throw new ArgumentException("The file path is invalid.", nameof(fullPath));
if (!Directory.Exists(dir))
@@ -108,7 +107,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
try
{
- return JsonConvert.DeserializeObject(json, this.JsonSettings);
+ return JsonConvert.DeserializeObject(json, this.JsonSettings)
+ ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
}
catch (JsonReaderException)
{
@@ -117,7 +117,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
try
{
- return JsonConvert.DeserializeObject(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings);
+ return JsonConvert.DeserializeObject(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings)
+ ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
}
catch { /* rethrow original error */ }
}
diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
index 64725818..5d1b73b1 100644
--- a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
+++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs
@@ -26,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// The unique mod ID to require.
/// The minimum required version (if any).
/// Whether the dependency must be installed to use the mod.
- public ManifestDependency(string uniqueID, string minimumVersion, bool required = true)
+ public ManifestDependency(string uniqueID, string? minimumVersion, bool required = true)
: this(
uniqueID: uniqueID,
minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion)
diff --git a/src/SMAPI.Toolkit/Serialization/SParseException.cs b/src/SMAPI.Toolkit/Serialization/SParseException.cs
index 1581e027..c2b3f68e 100644
--- a/src/SMAPI.Toolkit/Serialization/SParseException.cs
+++ b/src/SMAPI.Toolkit/Serialization/SParseException.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
namespace StardewModdingAPI.Toolkit.Serialization
@@ -13,7 +11,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// Construct an instance.
/// The error message.
/// The underlying exception, if any.
- public SParseException(string message, Exception ex = null)
+ public SParseException(string message, Exception? ex = null)
: base(message, ex) { }
}
}
diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
index f14678be..1791c5b3 100644
--- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
+++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs
@@ -1,7 +1,4 @@
-#nullable disable
-
using System;
-using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Toolkit.Framework;
namespace StardewModdingAPI.Toolkit.Utilities
@@ -36,7 +33,6 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// Get the human-readable OS name and version.
/// The current platform.
- [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
public static string GetFriendlyPlatformName(Platform platform)
{
return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString());
diff --git a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
index ba2d0f47..a6bf5929 100644
--- a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
+++ b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.IO;
using System.Security.Cryptography;
diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
index d035d4cd..9b7a78a0 100644
--- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
+++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs
@@ -1,6 +1,5 @@
-#nullable disable
-
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
@@ -38,8 +37,11 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// The path to split.
/// The number of segments to match. Any additional segments will be merged into the last returned part.
[Pure]
- public static string[] GetSegments(string path, int? limit = null)
+ public static string[] GetSegments(string? path, int? limit = null)
{
+ if (path == null)
+ return Array.Empty();
+
return limit.HasValue
? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries)
: path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries);
@@ -47,8 +49,14 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.
/// The asset name to normalize.
- public static string NormalizeAssetName(string assetName)
+ [Pure]
+ [return: NotNullIfNotNull("assetName")]
+ public static string? NormalizeAssetName(string? assetName)
{
+ assetName = assetName?.Trim();
+ if (string.IsNullOrEmpty(assetName))
+ return assetName;
+
return string.Join(PathUtilities.PreferredAssetSeparator.ToString(), PathUtilities.GetSegments(assetName)); // based on MonoGame's ContentManager.Load logic
}
@@ -56,7 +64,8 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// The file path to normalize.
/// This should only be used for file paths. For asset names, use instead.
[Pure]
- public static string NormalizePath(string path)
+ [return: NotNullIfNotNull("path")]
+ public static string? NormalizePath(string? path)
{
path = path?.Trim();
if (string.IsNullOrEmpty(path))
@@ -80,7 +89,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
}
// keep trailing separator
- if ((!hasRoot || segments.Any()) && PathUtilities.PossiblePathSeparators.Contains(path[path.Length - 1]))
+ if ((!hasRoot || segments.Any()) && PathUtilities.PossiblePathSeparators.Contains(path[^1]))
newPath += PathUtilities.PreferredPathSeparator;
return newPath;
@@ -98,7 +107,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain ../).
/// The path to check.
[Pure]
- public static bool IsSafeRelativePath(string path)
+ public static bool IsSafeRelativePath(string? path)
{
if (string.IsNullOrWhiteSpace(path))
return true;
@@ -111,9 +120,11 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).
/// The string to check.
[Pure]
- public static bool IsSlug(string str)
+ public static bool IsSlug(string? str)
{
- return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
+ return
+ string.IsNullOrWhiteSpace(str)
+ || !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase);
}
}
}
diff --git a/src/SMAPI/Framework/Serialization/ColorConverter.cs b/src/SMAPI/Framework/Serialization/ColorConverter.cs
index 8fb6cd9c..3b3720b5 100644
--- a/src/SMAPI/Framework/Serialization/ColorConverter.cs
+++ b/src/SMAPI/Framework/Serialization/ColorConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq;
diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs
index 9cf52228..f3bab20d 100644
--- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs
+++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -38,7 +36,7 @@ namespace StardewModdingAPI.Framework.Serialization
/// The object type.
/// The object being read.
/// The calling serializer.
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
string path = reader.Path;
@@ -76,7 +74,7 @@ namespace StardewModdingAPI.Framework.Serialization
/// The JSON writer.
/// The value.
/// The calling serializer.
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value?.ToString());
}
diff --git a/src/SMAPI/Framework/Serialization/PointConverter.cs b/src/SMAPI/Framework/Serialization/PointConverter.cs
index b48d757a..21d1f845 100644
--- a/src/SMAPI/Framework/Serialization/PointConverter.cs
+++ b/src/SMAPI/Framework/Serialization/PointConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq;
diff --git a/src/SMAPI/Framework/Serialization/RectangleConverter.cs b/src/SMAPI/Framework/Serialization/RectangleConverter.cs
index 7f060e3a..31f3ad77 100644
--- a/src/SMAPI/Framework/Serialization/RectangleConverter.cs
+++ b/src/SMAPI/Framework/Serialization/RectangleConverter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
diff --git a/src/SMAPI/Framework/Serialization/Vector2Converter.cs b/src/SMAPI/Framework/Serialization/Vector2Converter.cs
index bcd483d3..589febf8 100644
--- a/src/SMAPI/Framework/Serialization/Vector2Converter.cs
+++ b/src/SMAPI/Framework/Serialization/Vector2Converter.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System;
using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq;
diff --git a/src/SMAPI/Utilities/PathUtilities.cs b/src/SMAPI/Utilities/PathUtilities.cs
index e8ab9645..4350f441 100644
--- a/src/SMAPI/Utilities/PathUtilities.cs
+++ b/src/SMAPI/Utilities/PathUtilities.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities;
@@ -22,14 +21,16 @@ namespace StardewModdingAPI.Utilities
/// The path to split.
/// The number of segments to match. Any additional segments will be merged into the last returned part.
[Pure]
- public static string[] GetSegments(string path, int? limit = null)
+ public static string[] GetSegments(string? path, int? limit = null)
{
return ToolkitPathUtilities.GetSegments(path, limit);
}
/// Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.
/// The asset name to normalize.
- public static string NormalizeAssetName(string assetName)
+ [Pure]
+ [return: NotNullIfNotNull("assetName")]
+ public static string? NormalizeAssetName(string? assetName)
{
return ToolkitPathUtilities.NormalizeAssetName(assetName);
}
@@ -38,7 +39,8 @@ namespace StardewModdingAPI.Utilities
/// The file path to normalize.
/// This should only be used for file paths. For asset names, use instead.
[Pure]
- public static string NormalizePath(string path)
+ [return: NotNullIfNotNull("path")]
+ public static string? NormalizePath(string? path)
{
return ToolkitPathUtilities.NormalizePath(path);
}
@@ -46,7 +48,7 @@ namespace StardewModdingAPI.Utilities
/// Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain ../).
/// The path to check.
[Pure]
- public static bool IsSafeRelativePath(string path)
+ public static bool IsSafeRelativePath(string? path)
{
return ToolkitPathUtilities.IsSafeRelativePath(path);
}
@@ -54,7 +56,7 @@ namespace StardewModdingAPI.Utilities
/// Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).
/// The string to check.
[Pure]
- public static bool IsSlug(string str)
+ public static bool IsSlug(string? str)
{
return ToolkitPathUtilities.IsSlug(str);
}