enable nullable annotations for most of the SMAPI toolkit (#837)

This commit is contained in:
Jesse Plamondon-Willard 2022-04-07 02:33:23 -04:00
parent 6b05296e71
commit d706a25053
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
27 changed files with 126 additions and 148 deletions

View File

@ -1,5 +1,3 @@
#nullable disable
namespace StardewModdingAPI.Toolkit.Framework namespace StardewModdingAPI.Toolkit.Framework
{ {
/// <summary>Contains the SMAPI installer's constants and assumptions.</summary> /// <summary>Contains the SMAPI installer's constants and assumptions.</summary>

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -41,7 +39,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
IEnumerable<string> paths = this IEnumerable<string> paths = this
.GetCustomInstallPaths() .GetCustomInstallPaths()
.Concat(this.GetDefaultInstallPaths()) .Concat(this.GetDefaultInstallPaths())
.Select(PathUtilities.NormalizePath) .Select(path => PathUtilities.NormalizePath(path))
.Distinct(StringComparer.OrdinalIgnoreCase); .Distinct(StringComparer.OrdinalIgnoreCase);
// yield valid folders // yield valid folders
@ -80,10 +78,12 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
return GameFolderType.NoGameFound; return GameFolderType.NoGameFound;
// get assembly version // get assembly version
Version version; Version? version;
try try
{ {
version = AssemblyName.GetAssemblyName(executable.FullName).Version; version = AssemblyName.GetAssemblyName(executable.FullName).Version;
if (version == null)
return GameFolderType.InvalidUnknown;
} }
catch catch
{ {
@ -123,7 +123,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
case Platform.Linux: case Platform.Linux:
case Platform.Mac: case Platform.Mac:
{ {
string home = Environment.GetEnvironmentVariable("HOME"); string home = Environment.GetEnvironmentVariable("HOME")!;
// Linux // Linux
yield return $"{home}/GOG Games/Stardew Valley/game"; yield return $"{home}/GOG Games/Stardew Valley/game";
@ -148,13 +148,13 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
}; };
foreach (var pair in registryKeys) 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)) if (!string.IsNullOrWhiteSpace(path))
yield return path; yield return path;
} }
// via Steam library path // via Steam library path
string steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); string? steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath");
if (steamPath != null) if (steamPath != null)
yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley"); yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley");
#endif #endif
@ -188,7 +188,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
private IEnumerable<string> GetCustomInstallPaths() private IEnumerable<string> GetCustomInstallPaths()
{ {
// get home path // 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)) if (string.IsNullOrWhiteSpace(homePath))
yield break; yield break;
@ -210,7 +210,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
} }
// get install path // 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)) if (!string.IsNullOrWhiteSpace(element?.Value))
yield return element.Value.Trim(); yield return element.Value.Trim();
} }
@ -219,27 +219,27 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning
/// <summary>Get the value of a key in the Windows HKLM registry.</summary> /// <summary>Get the value of a key in the Windows HKLM registry.</summary>
/// <param name="key">The full path of the registry key relative to HKLM.</param> /// <param name="key">The full path of the registry key relative to HKLM.</param>
/// <param name="name">The name of the value.</param> /// <param name="name">The name of the value.</param>
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 localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine;
RegistryKey openKey = localMachine.OpenSubKey(key); RegistryKey? openKey = localMachine.OpenSubKey(key);
if (openKey == null) if (openKey == null)
return null; return null;
using (openKey) using (openKey)
return (string)openKey.GetValue(name); return (string?)openKey.GetValue(name);
} }
/// <summary>Get the value of a key in the Windows HKCU registry.</summary> /// <summary>Get the value of a key in the Windows HKCU registry.</summary>
/// <param name="key">The full path of the registry key relative to HKCU.</param> /// <param name="key">The full path of the registry key relative to HKCU.</param>
/// <param name="name">The name of the value.</param> /// <param name="name">The name of the value.</param>
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 currentUser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser;
RegistryKey openKey = currentuser.OpenSubKey(key); RegistryKey? openKey = currentUser.OpenSubKey(key);
if (openKey == null) if (openKey == null)
return null; return null;
using (openKey) using (openKey)
return (string)openKey.GetValue(name); return (string?)openKey.GetValue(name);
} }
#endif #endif
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -59,11 +57,13 @@ namespace StardewModdingAPI.Toolkit.Framework
#if SMAPI_FOR_WINDOWS #if SMAPI_FOR_WINDOWS
try try
{ {
return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") string? result = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem")
.Get() .Get()
.Cast<ManagementObject>() .Cast<ManagementObject>()
.Select(entry => entry.GetPropertyValue("Caption").ToString()) .Select(entry => entry.GetPropertyValue("Caption").ToString())
.FirstOrDefault(); .FirstOrDefault();
return result ?? "Windows";
} }
catch { } catch { }
#endif #endif
@ -137,7 +137,7 @@ namespace StardewModdingAPI.Toolkit.Framework
buffer = Marshal.AllocHGlobal(8192); buffer = Marshal.AllocHGlobal(8192);
if (LowLevelEnvironmentUtility.uname(buffer) == 0) if (LowLevelEnvironmentUtility.uname(buffer) == 0)
{ {
string os = Marshal.PtrToStringAnsi(buffer); string? os = Marshal.PtrToStringAnsi(buffer);
return os == "Darwin"; return os == "Darwin";
} }
return false; return false;

View File

@ -1,5 +1,3 @@
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -24,13 +22,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModType Type { get; } public ModType Type { get; }
/// <summary>The mod manifest.</summary> /// <summary>The mod manifest.</summary>
public Manifest Manifest { get; } public Manifest? Manifest { get; }
/// <summary>The error which occurred parsing the manifest, if any.</summary> /// <summary>The error which occurred parsing the manifest, if any.</summary>
public ModParseError ManifestParseError { get; set; } public ModParseError ManifestParseError { get; set; }
/// <summary>A human-readable message for the <see cref="ManifestParseError"/>, if any.</summary> /// <summary>A human-readable message for the <see cref="ManifestParseError"/>, if any.</summary>
public string ManifestParseErrorText { get; set; } public string? ManifestParseErrorText { get; set; }
/********* /*********
@ -51,7 +49,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
/// <param name="manifest">The mod manifest.</param> /// <param name="manifest">The mod manifest.</param>
/// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param> /// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param>
/// <param name="manifestParseErrorText">A human-readable message for the <paramref name="manifestParseError"/>, if any.</param> /// <param name="manifestParseErrorText">A human-readable message for the <paramref name="manifestParseError"/>, if any.</param>
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 // save info
this.Directory = directory; this.Directory = directory;
@ -61,9 +59,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
this.ManifestParseErrorText = manifestParseErrorText; this.ManifestParseErrorText = manifestParseErrorText;
// set display name // set display name
this.DisplayName = manifest?.Name; this.DisplayName = !string.IsNullOrWhiteSpace(manifest?.Name)
if (string.IsNullOrWhiteSpace(this.DisplayName)) ? manifest.Name
this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName); : PathUtilities.GetRelativePath(root.FullName, directory.FullName);
} }
/// <summary>Get the update keys for a mod.</summary> /// <summary>Get the update keys for a mod.</summary>

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -117,7 +115,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder) public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder)
{ {
// find manifest.json // find manifest.json
FileInfo manifestFile = this.FindManifest(searchFolder); FileInfo? manifestFile = this.FindManifest(searchFolder);
// set appropriate invalid-mod error // set appropriate invalid-mod error
if (manifestFile == null) if (manifestFile == null)
@ -147,13 +145,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
} }
// read mod info // read mod info
Manifest manifest = null; Manifest? manifest = null;
ModParseError error = ModParseError.None; ModParseError error = ModParseError.None;
string errorText = null; string? errorText = null;
{ {
try try
{ {
if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null) if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest))
{ {
error = ModParseError.ManifestInvalid; error = ModParseError.ManifestInvalid;
errorText = "its manifest is invalid."; errorText = "its manifest is invalid.";
@ -186,7 +184,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
} }
// build result // 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
/// <summary>Find the manifest for a mod folder.</summary> /// <summary>Find the manifest for a mod folder.</summary>
/// <param name="folder">The folder to search.</param> /// <param name="folder">The folder to search.</param>
private FileInfo FindManifest(DirectoryInfo folder) private FileInfo? FindManifest(DirectoryInfo folder)
{ {
while (true) while (true)
{ {

View File

@ -1,4 +1,4 @@
#nullable disable using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Toolkit.Framework namespace StardewModdingAPI.Toolkit.Framework
{ {
@ -18,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework
/// <param name="prereleaseTag">An optional prerelease tag.</param> /// <param name="prereleaseTag">An optional prerelease tag.</param>
/// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param> /// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param>
/// <returns>Returns whether the version was successfully parsed.</returns> /// <returns>Returns whether the version was successfully parsed.</returns>
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 // init
major = 0; major = 0;
@ -105,7 +105,7 @@ namespace StardewModdingAPI.Toolkit.Framework
/// <param name="raw">The raw characters to parse.</param> /// <param name="raw">The raw characters to parse.</param>
/// <param name="index">The index of the next character to read.</param> /// <param name="index">The index of the next character to read.</param>
/// <param name="tag">The parsed tag.</param> /// <param name="tag">The parsed tag.</param>
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 // read tag length
int length = 0; int length = 0;

View File

@ -1,6 +1,5 @@
#nullable disable
using System; using System;
using System.Diagnostics.CodeAnalysis;
namespace StardewModdingAPI.Toolkit.Framework.UpdateData namespace StardewModdingAPI.Toolkit.Framework.UpdateData
{ {
@ -17,10 +16,11 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
public ModSiteKey Site { get; } public ModSiteKey Site { get; }
/// <summary>The mod ID within the repository.</summary> /// <summary>The mod ID within the repository.</summary>
public string ID { get; } [MemberNotNullWhen(true, nameof(LooksValid))]
public string? ID { get; }
/// <summary>If specified, a substring in download names/descriptions to match.</summary> /// <summary>If specified, a substring in download names/descriptions to match.</summary>
public string Subkey { get; } public string? Subkey { get; }
/// <summary>Whether the update key seems to be valid.</summary> /// <summary>Whether the update key seems to be valid.</summary>
public bool LooksValid { get; } public bool LooksValid { get; }
@ -34,9 +34,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="site">The mod site containing the mod.</param> /// <param name="site">The mod site containing the mod.</param>
/// <param name="id">The mod ID within the site.</param> /// <param name="id">The mod ID within the site.</param>
/// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
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.Site = site;
this.ID = id?.Trim(); this.ID = id?.Trim();
this.Subkey = subkey?.Trim(); this.Subkey = subkey?.Trim();
@ -49,19 +49,19 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="site">The mod site containing the mod.</param> /// <param name="site">The mod site containing the mod.</param>
/// <param name="id">The mod ID within the site.</param> /// <param name="id">The mod ID within the site.</param>
/// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
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) { } : this(UpdateKey.GetString(site, id, subkey), site, id, subkey) { }
/// <summary>Parse a raw update key.</summary> /// <summary>Parse a raw update key.</summary>
/// <param name="raw">The raw update key to parse.</param> /// <param name="raw">The raw update key to parse.</param>
public static UpdateKey Parse(string raw) public static UpdateKey Parse(string? raw)
{ {
// extract site + ID // extract site + ID
string rawSite; string? rawSite;
string id; string? id;
{ {
string[] parts = raw?.Trim().Split(':'); string[]? parts = raw?.Trim().Split(':');
if (parts == null || parts.Length != 2) if (parts?.Length != 2)
return new UpdateKey(raw, ModSiteKey.Unknown, null, null); return new UpdateKey(raw, ModSiteKey.Unknown, null, null);
rawSite = parts[0].Trim(); rawSite = parts[0].Trim();
@ -71,7 +71,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
id = null; id = null;
// extract subkey // extract subkey
string subkey = null; string? subkey = null;
if (id != null) if (id != null)
{ {
string[] parts = id.Split('@'); string[] parts = id.Split('@');
@ -111,7 +111,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary> /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param> /// <param name="other">An object to compare with this object.</param>
public bool Equals(UpdateKey other) public bool Equals(UpdateKey? other)
{ {
if (!this.LooksValid) if (!this.LooksValid)
{ {
@ -129,7 +129,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <summary>Determines whether the specified object is equal to the current object.</summary> /// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param> /// <param name="obj">The object to compare with the current object.</param>
public override bool Equals(object obj) public override bool Equals(object? obj)
{ {
return obj is UpdateKey other && this.Equals(other); return obj is UpdateKey other && this.Equals(other);
} }
@ -145,7 +145,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="site">The mod site containing the mod.</param> /// <param name="site">The mod site containing the mod.</param>
/// <param name="id">The mod ID within the repository.</param> /// <param name="id">The mod ID within the repository.</param>
/// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param>
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(); return $"{site}:{id}{subkey}".Trim();
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -24,7 +22,7 @@ namespace StardewModdingAPI.Toolkit
private readonly string UserAgent; private readonly string UserAgent;
/// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary> /// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary>
private readonly IDictionary<ModSiteKey, string> VendorModUrls = new Dictionary<ModSiteKey, string>() private readonly Dictionary<ModSiteKey, string> VendorModUrls = new()
{ {
[ModSiteKey.Chucklefish] = "https://community.playstarbound.com/resources/{0}", [ModSiteKey.Chucklefish] = "https://community.playstarbound.com/resources/{0}",
[ModSiteKey.GitHub] = "https://github.com/{0}/releases", [ModSiteKey.GitHub] = "https://github.com/{0}/releases",
@ -45,7 +43,7 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Construct an instance.</summary> /// <summary>Construct an instance.</summary>
public ModToolkit() 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}"; this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}";
} }
@ -59,7 +57,7 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Extract mod metadata from the wiki compatibility list.</summary> /// <summary>Extract mod metadata from the wiki compatibility list.</summary>
public async Task<WikiModList> GetWikiCompatibilityListAsync() public async Task<WikiModList> GetWikiCompatibilityListAsync()
{ {
var client = new WikiClient(this.UserAgent); WikiClient client = new(this.UserAgent);
return await client.FetchModsAsync(); return await client.FetchModsAsync();
} }
@ -89,13 +87,13 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Get an update URL for an update key (if valid).</summary> /// <summary>Get an update URL for an update key (if valid).</summary>
/// <param name="updateKey">The update key.</param> /// <param name="updateKey">The update key.</param>
public string GetUpdateUrl(string updateKey) public string? GetUpdateUrl(string updateKey)
{ {
UpdateKey parsed = UpdateKey.Parse(updateKey); UpdateKey parsed = UpdateKey.Parse(updateKey);
if (!parsed.LooksValid) if (!parsed.LooksValid)
return null; 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 string.Format(urlTemplate, parsed.ID);
return null; return null;

View File

@ -91,7 +91,7 @@ namespace StardewModdingAPI.Toolkit
{ {
if (version == null) if (version == null)
throw new ArgumentNullException(nameof(version), "The input version string can't be 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."); throw new FormatException($"The input '{version}' isn't a valid semantic version.");
this.MajorVersion = major; this.MajorVersion = major;

View File

@ -1,5 +1,3 @@
#nullable disable
using System.Collections.Generic; using System.Collections.Generic;
namespace StardewModdingAPI.Toolkit namespace StardewModdingAPI.Toolkit
@ -18,7 +16,7 @@ namespace StardewModdingAPI.Toolkit
** Public methods ** Public methods
*********/ *********/
/// <inheritdoc /> /// <inheritdoc />
public int Compare(ISemanticVersion x, ISemanticVersion y) public int Compare(ISemanticVersion? x, ISemanticVersion? y)
{ {
if (object.ReferenceEquals(x, y)) if (object.ReferenceEquals(x, y))
return 0; return 0;

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using StardewModdingAPI.Toolkit.Serialization.Models; using StardewModdingAPI.Toolkit.Serialization.Models;
@ -35,7 +33,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param> /// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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<ManifestContentPackFor>(reader); return serializer.Deserialize<ManifestContentPackFor>(reader);
} }
@ -44,7 +42,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param> /// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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."); throw new InvalidOperationException("This converter does not write JSON.");
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -37,13 +35,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param> /// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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<ManifestDependency> result = new List<ManifestDependency>(); List<ManifestDependency> result = new List<ManifestDependency>();
foreach (JObject obj in JArray.Load(reader).Children<JObject>()) foreach (JObject obj in JArray.Load(reader).Children<JObject>())
{ {
string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID)); string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID))!; // will be validated separately if null
string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion)); string? minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion));
bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true; bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true;
result.Add(new ManifestDependency(uniqueID, minVersion, required)); result.Add(new ManifestDependency(uniqueID, minVersion, required));
} }
@ -54,7 +52,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param> /// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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."); throw new InvalidOperationException("This converter does not write JSON.");
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -41,15 +39,17 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param> /// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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; string path = reader.Path;
switch (reader.TokenType) switch (reader.TokenType)
{ {
case JsonToken.StartObject: case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader)); return this.ReadObject(JObject.Load(reader));
case JsonToken.String: case JsonToken.String:
return this.ReadString(JToken.Load(reader).Value<string>(), path); return this.ReadString(JToken.Load(reader).Value<string>(), path);
default: default:
throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path}).");
} }
@ -59,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="writer">The JSON writer.</param> /// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{ {
writer.WriteValue(value?.ToString()); writer.WriteValue(value?.ToString());
} }
@ -75,7 +75,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion)); int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion));
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion)); int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion)); int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion));
string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag)); string? prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag));
return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag); return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag);
} }
@ -83,11 +83,11 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <summary>Read a JSON string.</summary> /// <summary>Read a JSON string.</summary>
/// <param name="str">The JSON string value.</param> /// <param name="str">The JSON string value.</param>
/// <param name="path">The path to the current JSON node.</param> /// <param name="path">The path to the current JSON node.</param>
private ISemanticVersion ReadString(string str, string path) private ISemanticVersion? ReadString(string str, string path)
{ {
if (string.IsNullOrWhiteSpace(str)) if (string.IsNullOrWhiteSpace(str))
return null; 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})."); 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; return version;
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -27,21 +25,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T); return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T);
} }
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
/// <summary>Reads the JSON representation of the object.</summary> /// <summary>Reads the JSON representation of the object.</summary>
/// <param name="reader">The JSON reader.</param> /// <param name="reader">The JSON reader.</param>
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param> /// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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; string path = reader.Path;
switch (reader.TokenType) switch (reader.TokenType)
@ -60,6 +49,15 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
} }
} }
/// <summary>Writes the JSON representation of the object.</summary>
/// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}
/********* /*********
** Protected methods ** Protected methods

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -12,12 +10,12 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</param> /// <param name="obj">The JSON object to search.</param>
/// <param name="fieldName">The field name.</param> /// <param name="fieldName">The field name.</param>
public static T ValueIgnoreCase<T>(this JObject obj, string fieldName) public static T? ValueIgnoreCase<T>(this JObject obj, string fieldName)
{ {
JToken token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase); JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase);
return token != null return token != null
? token.Value<T>() ? token.Value<T>()
: default(T); : default;
} }
} }
} }

View File

@ -1,7 +1,6 @@
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@ -38,7 +37,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <returns>Returns false if the file doesn't exist, else true.</returns> /// <returns>Returns false if the file doesn't exist, else true.</returns>
/// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception> /// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception>
/// <exception cref="JsonReaderException">The file contains invalid JSON.</exception> /// <exception cref="JsonReaderException">The file contains invalid JSON.</exception>
public bool ReadJsonFileIfExists<TModel>(string fullPath, out TModel result) public bool ReadJsonFileIfExists<TModel>(string fullPath, [NotNullWhen(true)] out TModel? result)
{ {
// validate // validate
if (string.IsNullOrWhiteSpace(fullPath)) if (string.IsNullOrWhiteSpace(fullPath))
@ -52,7 +51,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
} }
catch (Exception ex) when (ex is DirectoryNotFoundException or FileNotFoundException) catch (Exception ex) when (ex is DirectoryNotFoundException or FileNotFoundException)
{ {
result = default(TModel); result = default;
return false; return false;
} }
@ -60,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
try try
{ {
result = this.Deserialize<TModel>(json); result = this.Deserialize<TModel>(json);
return true; return result != null;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -90,7 +89,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath));
// create directory if needed // create directory if needed
string dir = Path.GetDirectoryName(fullPath); string dir = Path.GetDirectoryName(fullPath)!;
if (dir == null) if (dir == null)
throw new ArgumentException("The file path is invalid.", nameof(fullPath)); throw new ArgumentException("The file path is invalid.", nameof(fullPath));
if (!Directory.Exists(dir)) if (!Directory.Exists(dir))
@ -108,7 +107,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{ {
try try
{ {
return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings); return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings)
?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
} }
catch (JsonReaderException) catch (JsonReaderException)
{ {
@ -117,7 +117,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{ {
try try
{ {
return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings); return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings)
?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON.");
} }
catch { /* rethrow original error */ } catch { /* rethrow original error */ }
} }

View File

@ -26,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="uniqueID">The unique mod ID to require.</param> /// <param name="uniqueID">The unique mod ID to require.</param>
/// <param name="minimumVersion">The minimum required version (if any).</param> /// <param name="minimumVersion">The minimum required version (if any).</param>
/// <param name="required">Whether the dependency must be installed to use the mod.</param> /// <param name="required">Whether the dependency must be installed to use the mod.</param>
public ManifestDependency(string uniqueID, string minimumVersion, bool required = true) public ManifestDependency(string uniqueID, string? minimumVersion, bool required = true)
: this( : this(
uniqueID: uniqueID, uniqueID: uniqueID,
minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion) minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion)

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
namespace StardewModdingAPI.Toolkit.Serialization namespace StardewModdingAPI.Toolkit.Serialization
@ -13,7 +11,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <summary>Construct an instance.</summary> /// <summary>Construct an instance.</summary>
/// <param name="message">The error message.</param> /// <param name="message">The error message.</param>
/// <param name="ex">The underlying exception, if any.</param> /// <param name="ex">The underlying exception, if any.</param>
public SParseException(string message, Exception ex = null) public SParseException(string message, Exception? ex = null)
: base(message, ex) { } : base(message, ex) { }
} }
} }

View File

@ -1,7 +1,4 @@
#nullable disable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using StardewModdingAPI.Toolkit.Framework; using StardewModdingAPI.Toolkit.Framework;
namespace StardewModdingAPI.Toolkit.Utilities namespace StardewModdingAPI.Toolkit.Utilities
@ -36,7 +33,6 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <summary>Get the human-readable OS name and version.</summary> /// <summary>Get the human-readable OS name and version.</summary>
/// <param name="platform">The current platform.</param> /// <param name="platform">The current platform.</param>
[SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")]
public static string GetFriendlyPlatformName(Platform platform) public static string GetFriendlyPlatformName(Platform platform)
{ {
return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString()); return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString());

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;

View File

@ -1,6 +1,5 @@
#nullable disable
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -38,8 +37,11 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <param name="path">The path to split.</param> /// <param name="path">The path to split.</param>
/// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param> /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param>
[Pure] [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<string>();
return limit.HasValue return limit.HasValue
? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries) ? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries)
: path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); : path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries);
@ -47,8 +49,14 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary> /// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary>
/// <param name="assetName">The asset name to normalize.</param> /// <param name="assetName">The asset name to normalize.</param>
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<T> logic return string.Join(PathUtilities.PreferredAssetSeparator.ToString(), PathUtilities.GetSegments(assetName)); // based on MonoGame's ContentManager.Load<T> logic
} }
@ -56,7 +64,8 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <param name="path">The file path to normalize.</param> /// <param name="path">The file path to normalize.</param>
/// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks> /// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks>
[Pure] [Pure]
public static string NormalizePath(string path) [return: NotNullIfNotNull("path")]
public static string? NormalizePath(string? path)
{ {
path = path?.Trim(); path = path?.Trim();
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
@ -80,7 +89,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
} }
// keep trailing separator // 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; newPath += PathUtilities.PreferredPathSeparator;
return newPath; return newPath;
@ -98,7 +107,7 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary> /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
/// <param name="path">The path to check.</param> /// <param name="path">The path to check.</param>
[Pure] [Pure]
public static bool IsSafeRelativePath(string path) public static bool IsSafeRelativePath(string? path)
{ {
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
return true; return true;
@ -111,9 +120,11 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary> /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary>
/// <param name="str">The string to check.</param> /// <param name="str">The string to check.</param>
[Pure] [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);
} }
} }
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -38,7 +36,7 @@ namespace StardewModdingAPI.Framework.Serialization
/// <param name="objectType">The object type.</param> /// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</param> /// <param name="existingValue">The object being read.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
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; string path = reader.Path;
@ -76,7 +74,7 @@ namespace StardewModdingAPI.Framework.Serialization
/// <param name="writer">The JSON writer.</param> /// <param name="writer">The JSON writer.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param> /// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{ {
writer.WriteValue(value?.ToString()); writer.WriteValue(value?.ToString());
} }

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;

View File

@ -1,5 +1,3 @@
#nullable disable
using System; using System;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;

View File

@ -1,5 +1,4 @@
#nullable disable using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities;
@ -22,14 +21,16 @@ namespace StardewModdingAPI.Utilities
/// <param name="path">The path to split.</param> /// <param name="path">The path to split.</param>
/// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param> /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param>
[Pure] [Pure]
public static string[] GetSegments(string path, int? limit = null) public static string[] GetSegments(string? path, int? limit = null)
{ {
return ToolkitPathUtilities.GetSegments(path, limit); return ToolkitPathUtilities.GetSegments(path, limit);
} }
/// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary> /// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary>
/// <param name="assetName">The asset name to normalize.</param> /// <param name="assetName">The asset name to normalize.</param>
public static string NormalizeAssetName(string assetName) [Pure]
[return: NotNullIfNotNull("assetName")]
public static string? NormalizeAssetName(string? assetName)
{ {
return ToolkitPathUtilities.NormalizeAssetName(assetName); return ToolkitPathUtilities.NormalizeAssetName(assetName);
} }
@ -38,7 +39,8 @@ namespace StardewModdingAPI.Utilities
/// <param name="path">The file path to normalize.</param> /// <param name="path">The file path to normalize.</param>
/// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks> /// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks>
[Pure] [Pure]
public static string NormalizePath(string path) [return: NotNullIfNotNull("path")]
public static string? NormalizePath(string? path)
{ {
return ToolkitPathUtilities.NormalizePath(path); return ToolkitPathUtilities.NormalizePath(path);
} }
@ -46,7 +48,7 @@ namespace StardewModdingAPI.Utilities
/// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary> /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary>
/// <param name="path">The path to check.</param> /// <param name="path">The path to check.</param>
[Pure] [Pure]
public static bool IsSafeRelativePath(string path) public static bool IsSafeRelativePath(string? path)
{ {
return ToolkitPathUtilities.IsSafeRelativePath(path); return ToolkitPathUtilities.IsSafeRelativePath(path);
} }
@ -54,7 +56,7 @@ namespace StardewModdingAPI.Utilities
/// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary> /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary>
/// <param name="str">The string to check.</param> /// <param name="str">The string to check.</param>
[Pure] [Pure]
public static bool IsSlug(string str) public static bool IsSlug(string? str)
{ {
return ToolkitPathUtilities.IsSlug(str); return ToolkitPathUtilities.IsSlug(str);
} }