From 3431f486a2ef93e86d8923c1a4651644110df81b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Jan 2022 18:15:42 -0500 Subject: normalize season names in SDate constructor --- src/SMAPI/Utilities/SDate.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index cd075dcc..e10a59f8 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -250,6 +250,8 @@ namespace StardewModdingAPI.Utilities /// One of the arguments has an invalid value (like day 35). private SDate(int day, string season, int year, bool allowDayZero) { + season = season?.Trim().ToLowerInvariant(); + // validate if (season == null) throw new ArgumentNullException(nameof(season)); @@ -277,7 +279,7 @@ namespace StardewModdingAPI.Utilities /// The year. private bool IsDayZero(int day, string season, int year) { - return day == 0 && season == "spring" && year == 1; + return day == 0 && season?.Trim().ToLower() == "spring" && year == 1; } /// Get the day of week for a given date. -- cgit From 4da9e954df3846d01aa0536f4e8143466a1d62f3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 11 Feb 2022 00:49:49 -0500 Subject: use Array.Empty to avoid unneeded array allocations --- src/SMAPI/Utilities/Keybind.cs | 4 ++-- src/SMAPI/Utilities/KeybindList.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 87b867a9..403ecf4a 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -48,7 +48,7 @@ namespace StardewModdingAPI.Utilities if (string.IsNullOrWhiteSpace(input)) { parsed = new Keybind(SButton.None); - errors = new string[0]; + errors = Array.Empty(); return true; } @@ -97,7 +97,7 @@ namespace StardewModdingAPI.Utilities else { parsed = new Keybind(buttons); - errors = new string[0]; + errors = Array.Empty(); return true; } } diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 28cae240..f8f569af 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -55,7 +55,7 @@ namespace StardewModdingAPI.Utilities if (string.IsNullOrWhiteSpace(input)) { parsed = new KeybindList(); - errors = new string[0]; + errors = Array.Empty(); return true; } @@ -83,7 +83,7 @@ namespace StardewModdingAPI.Utilities else { parsed = new KeybindList(keybinds.ToArray()); - errors = new string[0]; + errors = Array.Empty(); return true; } } -- cgit From 4e2d7f2550b05e410735f51beac76ed040178cf4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 1 Apr 2022 23:42:37 -0400 Subject: make mod file paths case-insensitive in all SMAPI APIs --- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 124 ++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/SMAPI/Utilities/CaseInsensitivePathCache.cs (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs new file mode 100644 index 00000000..1d947b53 --- /dev/null +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace StardewModdingAPI.Utilities +{ + /// Provides an API for case-insensitive relative path lookups within a root directory. + internal class CaseInsensitivePathCache + { + /********* + ** Fields + *********/ + /// The root directory path for relative paths. + private readonly string RootPath; + + /// A case-insensitive lookup of file paths within the . Each path is listed in both file path and asset name format, so it's usable in both contexts without needing to re-parse paths. + private readonly Lazy> RelativePathCache; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The root directory path for relative paths. + public CaseInsensitivePathCache(string rootPath) + { + this.RootPath = rootPath; + this.RelativePathCache = new(this.GetRelativePathCache); + } + + /// Get the exact capitalization for a given relative file path. + /// The relative path. + /// Returns the resolved path in file path format, else the normalized . + public string GetFilePath(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizePath(relativePath)); + } + + /// Get the exact capitalization for a given asset name. + /// The relative path. + /// Returns the resolved path in asset name format, else the normalized . + public string GetAssetName(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); + } + + /// Add a relative path that was just created by a SMAPI API. + /// The relative path. This must already be normalized in asset name or file path format. + public void Add(string relativePath) + { + // skip if cache isn't created yet (no need to add files manually in that case) + if (!this.RelativePathCache.IsValueCreated) + return; + + // skip if already cached + if (this.RelativePathCache.Value.ContainsKey(relativePath)) + return; + + // make sure path exists + relativePath = PathUtilities.NormalizePath(relativePath); + if (!File.Exists(Path.Combine(this.RootPath, relativePath))) + throw new InvalidOperationException($"Can't add relative path '{relativePath}' to the case-insensitive cache for '{this.RootPath}' because that file doesn't exist."); + + // cache path + this.CacheRawPath(this.RelativePathCache.Value, relativePath); + } + + + /********* + ** Private methods + *********/ + /// Get the exact capitalization for a given relative path. + /// The relative path. This must already be normalized into asset name or file path format (i.e. using or respectively). + /// Returns the resolved path in the same format if found, else returns the path as-is. + private string GetImpl(string relativePath) + { + // invalid path + if (string.IsNullOrWhiteSpace(relativePath)) + return relativePath; + + // already cached + if (this.RelativePathCache.Value.TryGetValue(relativePath, out string resolved)) + return resolved; + + // file exists but isn't cached for some reason + // cache it now so any later references to it are case-insensitive + if (File.Exists(Path.Combine(this.RootPath, relativePath))) + { + this.CacheRawPath(this.RelativePathCache.Value, relativePath); + return relativePath; + } + + // no such file, keep capitalization as-is + return relativePath; + } + + /// Get a case-insensitive lookup of file paths (see ). + private Dictionary GetRelativePathCache() + { + Dictionary cache = new(StringComparer.OrdinalIgnoreCase); + + foreach (string path in Directory.EnumerateFiles(this.RootPath, "*", SearchOption.AllDirectories)) + { + string relativePath = path.Substring(this.RootPath.Length + 1); + + this.CacheRawPath(cache, relativePath); + } + + return cache; + } + + /// Add a raw relative path to the cache. + /// The cache to update. + /// The relative path to cache, with its exact filesystem capitalization. + private void CacheRawPath(IDictionary cache, string relativePath) + { + string filePath = PathUtilities.NormalizePath(relativePath); + string assetName = PathUtilities.NormalizeAssetName(relativePath); + + cache[filePath] = filePath; + cache[assetName] = assetName; + } + } +} -- cgit From 0539bb8f3705e5c50d0e5790e2af97f39aed04b8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 18:25:00 -0400 Subject: simplify with newer pattern features --- src/SMAPI/Utilities/Keybind.cs | 4 ++-- src/SMAPI/Utilities/KeybindList.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 403ecf4a..7aefe686 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -118,11 +118,11 @@ namespace StardewModdingAPI.Utilities return SButtonState.None; // mix of held + pressed => pressed - if (states.All(p => p == SButtonState.Pressed || p == SButtonState.Held)) + if (states.All(p => p is SButtonState.Pressed or SButtonState.Held)) return SButtonState.Pressed; // mix of held + released => released - if (states.All(p => p == SButtonState.Held || p == SButtonState.Released)) + if (states.All(p => p is SButtonState.Held or SButtonState.Released)) return SButtonState.Released; // not down last tick or now diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index f8f569af..f24976f7 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -139,7 +139,7 @@ namespace StardewModdingAPI.Utilities public bool IsDown() { SButtonState state = this.GetState(); - return state == SButtonState.Pressed || state == SButtonState.Held; + return state is SButtonState.Pressed or SButtonState.Held; } /// Get whether the input binding was just pressed this tick. -- cgit From 077d8e4f401ad1806c6af0540f432366314a2300 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 18:25:00 -0400 Subject: remove some unused/redundant code --- src/SMAPI/Utilities/PerScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 20b8fbce..6b7153ac 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Utilities /// Remove all active values. public void ResetAllScreens() { - this.RemoveScreens(p => true); + this.RemoveScreens(_ => true); } -- cgit From 2e7c233f6c9bf6430672b39f970a3324deba79dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 21:48:55 -0400 Subject: enable nullable annotations by default (#837) This adds `#nullable disable` to all existing code (except where null is impossible like enum files), so it can be migrated incrementally. --- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 2 ++ src/SMAPI/Utilities/Keybind.cs | 2 ++ src/SMAPI/Utilities/KeybindList.cs | 2 ++ src/SMAPI/Utilities/PathUtilities.cs | 2 ++ src/SMAPI/Utilities/PerScreen.cs | 2 ++ src/SMAPI/Utilities/SDate.cs | 2 ++ 6 files changed, 12 insertions(+) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs index 1d947b53..4596fdce 100644 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 7aefe686..7b1acf1d 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index f24976f7..7b2c396b 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Utilities/PathUtilities.cs b/src/SMAPI/Utilities/PathUtilities.cs index 541b163c..e8ab9645 100644 --- a/src/SMAPI/Utilities/PathUtilities.cs +++ b/src/SMAPI/Utilities/PathUtilities.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.Contracts; using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 6b7153ac..afe3ba91 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index e10a59f8..b10bc3da 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using Newtonsoft.Json; -- cgit From d706a25053cdc5d9f1ccc2c09dc3913f835c3f78 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 7 Apr 2022 02:33:23 -0400 Subject: enable nullable annotations for most of the SMAPI toolkit (#837) --- src/SMAPI/Utilities/PathUtilities.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Utilities') 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); } -- cgit From f39da383a17b368e92fd243cf155b27ba42671f3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 20:24:14 -0400 Subject: enable nullable annotations in SMAPI where no logic changes are needed (#837) --- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 4 +- src/SMAPI/Utilities/Keybind.cs | 5 +-- src/SMAPI/Utilities/KeybindList.cs | 11 +++-- src/SMAPI/Utilities/SDate.cs | 55 +++++++++++-------------- 4 files changed, 32 insertions(+), 43 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs index 4596fdce..2ac1b9f9 100644 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -81,7 +79,7 @@ namespace StardewModdingAPI.Utilities return relativePath; // already cached - if (this.RelativePathCache.Value.TryGetValue(relativePath, out string resolved)) + if (this.RelativePathCache.Value.TryGetValue(relativePath, out string? resolved)) return resolved; // file exists but isn't cached for some reason diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 7b1acf1d..3455ce77 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework; @@ -44,7 +43,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind, if valid. /// The parse errors, if any. - public static bool TryParse(string input, out Keybind parsed, out string[] errors) + public static bool TryParse(string input, [NotNullWhen(true)] out Keybind? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 7b2c396b..18eeb9fd 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Toolkit.Serialization; @@ -42,7 +41,7 @@ namespace StardewModdingAPI.Utilities /// The format is invalid. public static KeybindList Parse(string input) { - return KeybindList.TryParse(input, out KeybindList parsed, out string[] errors) + return KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors) ? parsed : throw new SParseException($"Can't parse {nameof(Keybind)} from invalid value '{input}'.\n{string.Join("\n", errors)}"); } @@ -51,7 +50,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind list, if valid. /// The errors that occurred while parsing the input, if any. - public static bool TryParse(string input, out KeybindList parsed, out string[] errors) + public static bool TryParse(string input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) @@ -69,7 +68,7 @@ namespace StardewModdingAPI.Utilities if (string.IsNullOrWhiteSpace(rawSet)) continue; - if (!Keybind.TryParse(rawSet, out Keybind keybind, out string[] curErrors)) + if (!Keybind.TryParse(rawSet, out Keybind? keybind, out string[] curErrors)) rawErrors.AddRange(curErrors); else keybinds.Add(keybind); @@ -151,7 +150,7 @@ namespace StardewModdingAPI.Utilities } /// Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned. - public Keybind GetKeybindCurrentlyDown() + public Keybind? GetKeybindCurrentlyDown() { return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown()); } diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index b10bc3da..1d4e4489 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; using StardewModdingAPI.Framework; @@ -24,7 +23,7 @@ namespace StardewModdingAPI.Utilities private readonly int DaysInSeason = 28; /// The core SMAPI translations. - internal static Translator Translations; + internal static Translator? Translations; /********* @@ -94,7 +93,8 @@ namespace StardewModdingAPI.Utilities /// Get a date from a game date instance. /// The world date. - public static SDate From(WorldDate date) + [return: NotNullIfNotNull("date")] + public static SDate? From(WorldDate? date) { if (date == null) return null; @@ -170,14 +170,14 @@ namespace StardewModdingAPI.Utilities ****/ /// Get whether this instance is equal to another. /// The other value to compare. - public bool Equals(SDate other) + public bool Equals(SDate? other) { return this == other; } /// Get whether this instance is equal to another. /// The other value to compare. - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SDate other && this == other; } @@ -195,7 +195,7 @@ namespace StardewModdingAPI.Utilities /// The base date to compare. /// The other date to compare. /// The equality of the dates - public static bool operator ==(SDate date, SDate other) + public static bool operator ==(SDate? date, SDate? other) { return date?.DaysSinceStart == other?.DaysSinceStart; } @@ -203,7 +203,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is not equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator !=(SDate date, SDate other) + public static bool operator !=(SDate? date, SDate? other) { return date?.DaysSinceStart != other?.DaysSinceStart; } @@ -211,7 +211,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is more than another. /// The base date to compare. /// The other date to compare. - public static bool operator >(SDate date, SDate other) + public static bool operator >(SDate? date, SDate? other) { return date?.DaysSinceStart > other?.DaysSinceStart; } @@ -219,7 +219,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is more than or equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator >=(SDate date, SDate other) + public static bool operator >=(SDate? date, SDate? other) { return date?.DaysSinceStart >= other?.DaysSinceStart; } @@ -227,7 +227,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is less than or equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator <=(SDate date, SDate other) + public static bool operator <=(SDate? date, SDate? other) { return date?.DaysSinceStart <= other?.DaysSinceStart; } @@ -235,7 +235,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is less than another. /// The base date to compare. /// The other date to compare. - public static bool operator <(SDate date, SDate other) + public static bool operator <(SDate? date, SDate? other) { return date?.DaysSinceStart < other?.DaysSinceStart; } @@ -250,9 +250,10 @@ namespace StardewModdingAPI.Utilities /// The year. /// Whether to allow 0 spring Y1 as a valid date. /// One of the arguments has an invalid value (like day 35). + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this constructor.")] private SDate(int day, string season, int year, bool allowDayZero) { - season = season?.Trim().ToLowerInvariant(); + season = season?.Trim().ToLowerInvariant()!; // null-checked below // validate if (season == null) @@ -288,25 +289,17 @@ namespace StardewModdingAPI.Utilities /// The day of month. private DayOfWeek GetDayOfWeek(int day) { - switch (day % 7) + return (day % 7) switch { - case 0: - return DayOfWeek.Sunday; - case 1: - return DayOfWeek.Monday; - case 2: - return DayOfWeek.Tuesday; - case 3: - return DayOfWeek.Wednesday; - case 4: - return DayOfWeek.Thursday; - case 5: - return DayOfWeek.Friday; - case 6: - return DayOfWeek.Saturday; - default: - return 0; - } + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => 0 + }; } /// Get the number of days since the game began (starting at 1 for the first day of spring in Y1). -- cgit From 4adf8611131a5d86b15f017a42a0366837d14528 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 21:07:43 -0400 Subject: enable nullable annotations in the rest of SMAPI core (#837) --- src/SMAPI/Utilities/KeybindList.cs | 2 +- src/SMAPI/Utilities/PerScreen.cs | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 18eeb9fd..aa12a37a 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind list, if valid. /// The errors that occurred while parsing the input, if any. - public static bool TryParse(string input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) + public static bool TryParse(string? input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index afe3ba91..799ff63b 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,8 +1,7 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; +using StardewModdingAPI.Framework; namespace StardewModdingAPI.Utilities { @@ -39,14 +38,28 @@ namespace StardewModdingAPI.Utilities ** Public methods *********/ /// Construct an instance. + /// Limitation with nullable reference types: when the underlying type is nullable, this sets the default value to null regardless of whether you marked the type parameter nullable. To avoid that, set the default value with the 'createNewState' argument instead. public PerScreen() - : this(null) { } + : this(null!) { } /// Construct an instance. /// Create the initial state for a screen. public PerScreen(Func createNewState) { - this.CreateNewState = createNewState ?? (() => default); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- required for backwards compatibility + if (createNewState is null) + { + SCore.DeprecationManager.Warn( + SCore.DeprecationManager.GetSourceNameFromStack(), + $"calling the {nameof(PerScreen)} constructor with null", + "3.14.0", + DeprecationLevel.Notice + ); + + createNewState = (() => default!); + } + + this.CreateNewState = createNewState; } /// Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet. @@ -61,7 +74,7 @@ namespace StardewModdingAPI.Utilities public T GetValueForScreen(int screenId) { this.RemoveDeadScreens(); - return this.States.TryGetValue(screenId, out T state) + return this.States.TryGetValue(screenId, out T? state) ? state : this.States[screenId] = this.CreateNewState(); } -- cgit From 66272cbe46b848c64859cd0ab8cca6032c307137 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 23:40:47 -0400 Subject: fix false-positive deprecation notice (#837) --- src/SMAPI/Utilities/PerScreen.cs | 43 ++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 799ff63b..ba4f3325 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -40,27 +40,12 @@ namespace StardewModdingAPI.Utilities /// Construct an instance. /// Limitation with nullable reference types: when the underlying type is nullable, this sets the default value to null regardless of whether you marked the type parameter nullable. To avoid that, set the default value with the 'createNewState' argument instead. public PerScreen() - : this(null!) { } + : this(null, nullExpected: true) { } /// Construct an instance. /// Create the initial state for a screen. public PerScreen(Func createNewState) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- required for backwards compatibility - if (createNewState is null) - { - SCore.DeprecationManager.Warn( - SCore.DeprecationManager.GetSourceNameFromStack(), - $"calling the {nameof(PerScreen)} constructor with null", - "3.14.0", - DeprecationLevel.Notice - ); - - createNewState = (() => default!); - } - - this.CreateNewState = createNewState; - } + : this(createNewState, nullExpected: false) { } /// Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet. public IEnumerable> GetActiveValues() @@ -98,6 +83,30 @@ namespace StardewModdingAPI.Utilities /********* ** Private methods *********/ + /// Construct an instance. + /// Create the initial state for a screen. + /// Whether a null value is expected. + /// This constructor only exists to maintain backwards compatibility. In SMAPI 4.0.0, the overload that passes nullExpected: false should throw an exception instead. + private PerScreen(Func? createNewState, bool nullExpected) + { + if (createNewState is null) + { + createNewState = (() => default!); + + if (!nullExpected) + { + SCore.DeprecationManager.Warn( + SCore.DeprecationManager.GetSourceNameFromStack(), + $"calling the {nameof(PerScreen)} constructor with null", + "3.14.0", + DeprecationLevel.Notice + ); + } + } + + this.CreateNewState = createNewState; + } + /// Remove screens which are no longer active. private void RemoveDeadScreens() { -- cgit From fd136d34c5d4fbfc708eabf82a5eb9396d8a4756 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 Apr 2022 23:11:41 -0400 Subject: track full mod & stack metadata in queued deprecation warnings --- src/SMAPI/Utilities/PerScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index ba4f3325..6c2e436b 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -96,7 +96,7 @@ namespace StardewModdingAPI.Utilities if (!nullExpected) { SCore.DeprecationManager.Warn( - SCore.DeprecationManager.GetSourceNameFromStack(), + SCore.DeprecationManager.GetModFromStack(), $"calling the {nameof(PerScreen)} constructor with null", "3.14.0", DeprecationLevel.Notice -- cgit From 1974324c43b093a360507546e8be12ad594b56f2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 Apr 2022 13:41:37 -0400 Subject: make EntryDll manifest field case-insensitive --- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs index 2ac1b9f9..04fdcfae 100644 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -16,6 +16,9 @@ namespace StardewModdingAPI.Utilities /// A case-insensitive lookup of file paths within the . Each path is listed in both file path and asset name format, so it's usable in both contexts without needing to re-parse paths. private readonly Lazy> RelativePathCache; + /// The case-insensitive path caches by root path. + private static readonly Dictionary CachesByRootPath = new(StringComparer.OrdinalIgnoreCase); + /********* ** Public methods @@ -65,6 +68,18 @@ namespace StardewModdingAPI.Utilities this.CacheRawPath(this.RelativePathCache.Value, relativePath); } + /// Get a cached dictionary of relative paths within a root path, for case-insensitive file lookups. + /// The root path to scan. + public static CaseInsensitivePathCache GetFor(string rootPath) + { + rootPath = PathUtilities.NormalizePath(rootPath); + + if (!CaseInsensitivePathCache.CachesByRootPath.TryGetValue(rootPath, out CaseInsensitivePathCache? cache)) + CaseInsensitivePathCache.CachesByRootPath[rootPath] = cache = new CaseInsensitivePathCache(rootPath); + + return cache; + } + /********* ** Private methods @@ -82,15 +97,13 @@ namespace StardewModdingAPI.Utilities if (this.RelativePathCache.Value.TryGetValue(relativePath, out string? resolved)) return resolved; - // file exists but isn't cached for some reason - // cache it now so any later references to it are case-insensitive + // keep capitalization as-is if (File.Exists(Path.Combine(this.RootPath, relativePath))) { + // file exists but isn't cached for some reason + // cache it now so any later references to it are case-insensitive this.CacheRawPath(this.RelativePathCache.Value, relativePath); - return relativePath; } - - // no such file, keep capitalization as-is return relativePath; } -- cgit From 95d7ba8935ac7214805147e694353206a56bddb7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 Apr 2022 14:07:09 -0400 Subject: move case-insensitive path lookup into toolkit for reuse --- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 137 ------------------------ 1 file changed, 137 deletions(-) delete mode 100644 src/SMAPI/Utilities/CaseInsensitivePathCache.cs (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs deleted file mode 100644 index 04fdcfae..00000000 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace StardewModdingAPI.Utilities -{ - /// Provides an API for case-insensitive relative path lookups within a root directory. - internal class CaseInsensitivePathCache - { - /********* - ** Fields - *********/ - /// The root directory path for relative paths. - private readonly string RootPath; - - /// A case-insensitive lookup of file paths within the . Each path is listed in both file path and asset name format, so it's usable in both contexts without needing to re-parse paths. - private readonly Lazy> RelativePathCache; - - /// The case-insensitive path caches by root path. - private static readonly Dictionary CachesByRootPath = new(StringComparer.OrdinalIgnoreCase); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The root directory path for relative paths. - public CaseInsensitivePathCache(string rootPath) - { - this.RootPath = rootPath; - this.RelativePathCache = new(this.GetRelativePathCache); - } - - /// Get the exact capitalization for a given relative file path. - /// The relative path. - /// Returns the resolved path in file path format, else the normalized . - public string GetFilePath(string relativePath) - { - return this.GetImpl(PathUtilities.NormalizePath(relativePath)); - } - - /// Get the exact capitalization for a given asset name. - /// The relative path. - /// Returns the resolved path in asset name format, else the normalized . - public string GetAssetName(string relativePath) - { - return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); - } - - /// Add a relative path that was just created by a SMAPI API. - /// The relative path. This must already be normalized in asset name or file path format. - public void Add(string relativePath) - { - // skip if cache isn't created yet (no need to add files manually in that case) - if (!this.RelativePathCache.IsValueCreated) - return; - - // skip if already cached - if (this.RelativePathCache.Value.ContainsKey(relativePath)) - return; - - // make sure path exists - relativePath = PathUtilities.NormalizePath(relativePath); - if (!File.Exists(Path.Combine(this.RootPath, relativePath))) - throw new InvalidOperationException($"Can't add relative path '{relativePath}' to the case-insensitive cache for '{this.RootPath}' because that file doesn't exist."); - - // cache path - this.CacheRawPath(this.RelativePathCache.Value, relativePath); - } - - /// Get a cached dictionary of relative paths within a root path, for case-insensitive file lookups. - /// The root path to scan. - public static CaseInsensitivePathCache GetFor(string rootPath) - { - rootPath = PathUtilities.NormalizePath(rootPath); - - if (!CaseInsensitivePathCache.CachesByRootPath.TryGetValue(rootPath, out CaseInsensitivePathCache? cache)) - CaseInsensitivePathCache.CachesByRootPath[rootPath] = cache = new CaseInsensitivePathCache(rootPath); - - return cache; - } - - - /********* - ** Private methods - *********/ - /// Get the exact capitalization for a given relative path. - /// The relative path. This must already be normalized into asset name or file path format (i.e. using or respectively). - /// Returns the resolved path in the same format if found, else returns the path as-is. - private string GetImpl(string relativePath) - { - // invalid path - if (string.IsNullOrWhiteSpace(relativePath)) - return relativePath; - - // already cached - if (this.RelativePathCache.Value.TryGetValue(relativePath, out string? resolved)) - return resolved; - - // keep capitalization as-is - if (File.Exists(Path.Combine(this.RootPath, relativePath))) - { - // file exists but isn't cached for some reason - // cache it now so any later references to it are case-insensitive - this.CacheRawPath(this.RelativePathCache.Value, relativePath); - } - return relativePath; - } - - /// Get a case-insensitive lookup of file paths (see ). - private Dictionary GetRelativePathCache() - { - Dictionary cache = new(StringComparer.OrdinalIgnoreCase); - - foreach (string path in Directory.EnumerateFiles(this.RootPath, "*", SearchOption.AllDirectories)) - { - string relativePath = path.Substring(this.RootPath.Length + 1); - - this.CacheRawPath(cache, relativePath); - } - - return cache; - } - - /// Add a raw relative path to the cache. - /// The cache to update. - /// The relative path to cache, with its exact filesystem capitalization. - private void CacheRawPath(IDictionary cache, string relativePath) - { - string filePath = PathUtilities.NormalizePath(relativePath); - string assetName = PathUtilities.NormalizeAssetName(relativePath); - - cache[filePath] = filePath; - cache[assetName] = assetName; - } - } -} -- cgit From 889004f1eba31aa3a5069e1dcbe79896d05720b0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 19 Apr 2022 19:03:47 -0400 Subject: move deprecation code into namespace --- src/SMAPI/Utilities/PerScreen.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/SMAPI/Utilities') diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 6c2e436b..1c4c56fe 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Deprecations; namespace StardewModdingAPI.Utilities { -- cgit