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
{
/// <summary>Contains the SMAPI installer's constants and assumptions.</summary>

View File

@ -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<string> 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<string> 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
/// <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="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 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);
}
/// <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="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 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
}

View File

@ -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<ManagementObject>()
.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;

View File

@ -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; }
/// <summary>The mod manifest.</summary>
public Manifest Manifest { get; }
public Manifest? Manifest { get; }
/// <summary>The error which occurred parsing the manifest, if any.</summary>
public ModParseError ManifestParseError { get; set; }
/// <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="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>
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);
}
/// <summary>Get the update keys for a mod.</summary>

View File

@ -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<Manifest>(manifestFile.FullName, out manifest) || manifest == null)
if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(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
/// <summary>Find the manifest for a mod folder.</summary>
/// <param name="folder">The folder to search.</param>
private FileInfo FindManifest(DirectoryInfo folder)
private FileInfo? FindManifest(DirectoryInfo folder)
{
while (true)
{

View File

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

View File

@ -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; }
/// <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>
public string Subkey { get; }
public string? Subkey { get; }
/// <summary>Whether the update key seems to be valid.</summary>
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="id">The mod ID within the site.</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.ID = id?.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="id">The mod ID within the site.</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) { }
/// <summary>Parse a raw update key.</summary>
/// <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
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
/// <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>
public bool Equals(UpdateKey other)
public bool Equals(UpdateKey? other)
{
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>
/// <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);
}
@ -145,7 +145,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData
/// <param name="site">The mod site containing the mod.</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>
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();
}

View File

@ -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;
/// <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.GitHub] = "https://github.com/{0}/releases",
@ -45,7 +43,7 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Construct an instance.</summary>
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
/// <summary>Extract mod metadata from the wiki compatibility list.</summary>
public async Task<WikiModList> GetWikiCompatibilityListAsync()
{
var client = new WikiClient(this.UserAgent);
WikiClient client = new(this.UserAgent);
return await client.FetchModsAsync();
}
@ -89,13 +87,13 @@ namespace StardewModdingAPI.Toolkit
/// <summary>Get an update URL for an update key (if valid).</summary>
/// <param name="updateKey">The update key.</param>
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;

View File

@ -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;

View File

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

View File

@ -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
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</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);
}
@ -44,7 +42,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <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)
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new InvalidOperationException("This converter does not write JSON.");
}

View File

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

View File

@ -1,5 +1,3 @@
#nullable disable
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -41,15 +39,17 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</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;
switch (reader.TokenType)
{
case JsonToken.StartObject:
return this.ReadObject(JObject.Load(reader));
case JsonToken.String:
return this.ReadString(JToken.Load(reader).Value<string>(), 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
/// <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)
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<int>(nameof(ISemanticVersion.MajorVersion));
int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion));
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);
}
@ -83,11 +83,11 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters
/// <summary>Read a JSON string.</summary>
/// <param name="str">The JSON string value.</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))
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;
}

View File

@ -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);
}
/// <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>
/// <param name="reader">The JSON reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The object being read.</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;
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

View File

@ -1,5 +1,3 @@
#nullable disable
using System;
using Newtonsoft.Json.Linq;
@ -12,12 +10,12 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <typeparam name="T">The value type.</typeparam>
/// <param name="obj">The JSON object to search.</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
? token.Value<T>()
: default(T);
: default;
}
}
}

View File

@ -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>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="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
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<TModel>(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<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)
{
@ -117,7 +117,8 @@ namespace StardewModdingAPI.Toolkit.Serialization
{
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 */ }
}

View File

@ -26,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
/// <param name="uniqueID">The unique mod ID to require.</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>
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)

View File

@ -1,5 +1,3 @@
#nullable disable
using System;
namespace StardewModdingAPI.Toolkit.Serialization
@ -13,7 +11,7 @@ namespace StardewModdingAPI.Toolkit.Serialization
/// <summary>Construct an instance.</summary>
/// <param name="message">The error message.</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) { }
}
}

View File

@ -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
/// <summary>Get the human-readable OS name and version.</summary>
/// <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)
{
return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString());

View File

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

View File

@ -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
/// <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>
[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
? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, 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>
/// <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
}
@ -56,7 +64,8 @@ namespace StardewModdingAPI.Toolkit.Utilities
/// <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>
[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
/// <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>
[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
/// <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>
[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 Microsoft.Xna.Framework;
using Newtonsoft.Json.Linq;

View File

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

View File

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

View File

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

View File

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

View File

@ -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
/// <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>
[Pure]
public static string[] GetSegments(string path, int? limit = null)
public static string[] GetSegments(string? path, int? limit = null)
{
return ToolkitPathUtilities.GetSegments(path, limit);
}
/// <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>
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
/// <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>
[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
/// <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>
[Pure]
public static bool IsSafeRelativePath(string path)
public static bool IsSafeRelativePath(string? 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>
/// <param name="str">The string to check.</param>
[Pure]
public static bool IsSlug(string str)
public static bool IsSlug(string? str)
{
return ToolkitPathUtilities.IsSlug(str);
}