diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 20:24:14 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 20:24:14 -0400 |
commit | f39da383a17b368e92fd243cf155b27ba42671f3 (patch) | |
tree | 56c215dfb34da270a7714afd141e76a94c69a2c0 /src/SMAPI/Framework | |
parent | 6e9e8aef1ef97e1a4ef4410ce300cb1c47eca986 (diff) | |
download | SMAPI-f39da383a17b368e92fd243cf155b27ba42671f3.tar.gz SMAPI-f39da383a17b368e92fd243cf155b27ba42671f3.tar.bz2 SMAPI-f39da383a17b368e92fd243cf155b27ba42671f3.zip |
enable nullable annotations in SMAPI where no logic changes are needed (#837)
Diffstat (limited to 'src/SMAPI/Framework')
79 files changed, 369 insertions, 466 deletions
diff --git a/src/SMAPI/Framework/Command.cs b/src/SMAPI/Framework/Command.cs index 776ba238..dca1dd09 100644 --- a/src/SMAPI/Framework/Command.cs +++ b/src/SMAPI/Framework/Command.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework @@ -11,7 +9,7 @@ namespace StardewModdingAPI.Framework ** Accessor *********/ /// <summary>The mod that registered the command (or <c>null</c> if registered by SMAPI).</summary> - public IModMetadata Mod { get; } + public IModMetadata? Mod { get; } /// <summary>The command name, which the user must type to trigger it.</summary> public string Name { get; } @@ -31,7 +29,7 @@ namespace StardewModdingAPI.Framework /// <param name="name">The command name, which the user must type to trigger it.</param> /// <param name="documentation">The human-readable documentation shown when the player runs the built-in 'help' command.</param> /// <param name="callback">The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user.</param> - public Command(IModMetadata mod, string name, string documentation, Action<string, string[]> callback) + public Command(IModMetadata? mod, string name, string documentation, Action<string, string[]> callback) { this.Mod = mod; this.Name = name; diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index fcfa928e..6dc6f131 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -73,9 +71,9 @@ namespace StardewModdingAPI.Framework.Commands private IEnumerable<SearchResult> FilterPatches(string[] searchTerms) { bool hasSearch = searchTerms.Any(); - bool IsMatch(string target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1); + bool IsMatch(string? target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1); - foreach (var patch in this.GetAllPatches()) + foreach (SearchResult patch in this.GetAllPatches()) { // matches entire patch if (IsMatch(patch.MethodDescription)) @@ -85,10 +83,10 @@ namespace StardewModdingAPI.Framework.Commands } // matches individual patchers - foreach (var pair in patch.PatchTypesByOwner.ToArray()) + foreach ((string patcherId, ISet<PatchType> patchTypes) in patch.PatchTypesByOwner.ToArray()) { - if (!IsMatch(pair.Key) && !pair.Value.Any(type => IsMatch(type.ToString()))) - patch.PatchTypesByOwner.Remove(pair.Key); + if (!IsMatch(patcherId) && !patchTypes.Any(type => IsMatch(type.ToString()))) + patch.PatchTypesByOwner.Remove(patcherId); } if (patch.PatchTypesByOwner.Any()) @@ -114,13 +112,13 @@ namespace StardewModdingAPI.Framework.Commands // get patch types by owner var typesByOwner = new Dictionary<string, ISet<PatchType>>(); - foreach (var group in patchGroups) + foreach ((PatchType type, IReadOnlyCollection<Patch> patches) in patchGroups) { - foreach (var patch in group.Value) + foreach (Patch patch in patches) { - if (!typesByOwner.TryGetValue(patch.owner, out ISet<PatchType> patchTypes)) + if (!typesByOwner.TryGetValue(patch.owner, out ISet<PatchType>? patchTypes)) typesByOwner[patch.owner] = patchTypes = new HashSet<PatchType>(); - patchTypes.Add(group.Key); + patchTypes.Add(type); } } diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs index be4a7ce6..0367e999 100644 --- a/src/SMAPI/Framework/Content/AssetData.cs +++ b/src/SMAPI/Framework/Content/AssetData.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.Content @@ -7,12 +5,13 @@ namespace StardewModdingAPI.Framework.Content /// <summary>Base implementation for a content helper which encapsulates access and changes to content being read from a data file.</summary> /// <typeparam name="TValue">The interface value type.</typeparam> internal class AssetData<TValue> : AssetInfo, IAssetData<TValue> + where TValue : notnull { /********* ** Fields *********/ /// <summary>A callback to invoke when the data is replaced (if any).</summary> - private readonly Action<TValue> OnDataReplaced; + private readonly Action<TValue>? OnDataReplaced; /********* @@ -31,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="data">The content data being read.</param> /// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param> /// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param> - public AssetData(string locale, IAssetName assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue> onDataReplaced) + public AssetData(string? locale, IAssetName assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue>? onDataReplaced) : base(locale, assetName, data.GetType(), getNormalizedPath) { this.Data = data; diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index 06dbe259..d9bfa7bf 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="data">The content data being read.</param> /// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param> /// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param> - public AssetDataForDictionary(string locale, IAssetName assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced) + public AssetDataForDictionary(string? locale, IAssetName assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } } } diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 8e59cd27..97729c95 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="data">The content data being read.</param> /// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param> /// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param> - public AssetDataForImage(string locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced) + public AssetDataForImage(string? locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// <inheritdoc /> diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index bb3966b9..e508ca30 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="data">The content data being read.</param> /// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param> /// <param name="reflection">Simplifies access to private code.</param> - public AssetDataForObject(string locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath, Reflector reflection) + public AssetDataForObject(string? locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath, Reflector reflection) : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { this.Reflection = reflection; diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs index 1b7d0c93..464948b0 100644 --- a/src/SMAPI/Framework/Content/AssetEditOperation.cs +++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.Content public AssetEditPriority Priority { get; } /// <summary>The content pack on whose behalf the edit is being applied, if any.</summary> - public IModMetadata OnBehalfOf { get; } + public IModMetadata? OnBehalfOf { get; } /// <summary>Apply the edit to an asset.</summary> public Action<IAssetData> ApplyEdit { get; } @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="priority">If there are multiple edits that apply to the same asset, the priority with which this one should be applied.</param> /// <param name="onBehalfOf">The content pack on whose behalf the edit is being applied, if any.</param> /// <param name="applyEdit">Apply the edit to an asset.</param> - public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata onBehalfOf, Action<IAssetData> applyEdit) + public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata? onBehalfOf, Action<IAssetData> applyEdit) { this.Mod = mod; this.Priority = priority; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 51dcc61f..0f0e9bf3 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.Content ** Accessors *********/ /// <inheritdoc /> - public string Locale { get; } + public string? Locale { get; } /// <inheritdoc /> public IAssetName Name { get; } @@ -28,7 +26,7 @@ namespace StardewModdingAPI.Framework.Content public IAssetName NameWithoutLocale { get; } /// <inheritdoc /> - [Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")] public string AssetName { get @@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="assetName">The asset name being read.</param> /// <param name="type">The content type being read.</param> /// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param> - public AssetInfo(string locale, IAssetName assetName, Type type, Func<string, string> getNormalizedPath) + public AssetInfo(string? locale, IAssetName assetName, Type type, Func<string, string> getNormalizedPath) { this.Locale = locale; this.Name = assetName; @@ -66,7 +64,7 @@ namespace StardewModdingAPI.Framework.Content } /// <inheritdoc /> - [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(AssetInfo.NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")] public bool AssetNameEquals(string path) { SCore.DeprecationManager.Warn( @@ -106,7 +104,7 @@ namespace StardewModdingAPI.Framework.Content return "string"; // default - return type.FullName; + return type.FullName!; } } } diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 7f53db9b..fc8199e8 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -1,9 +1,8 @@ -#nullable disable - using System; using System.Reflection; using StardewModdingAPI.Internal; +#pragma warning disable CS0618 // obsolete asset interceptors deliberately supported here namespace StardewModdingAPI.Framework.Content { /// <summary>A wrapper for <see cref="IAssetEditor"/> and <see cref="IAssetLoader"/> for internal cache invalidation.</summary> @@ -46,11 +45,11 @@ namespace StardewModdingAPI.Framework.Content /// <param name="asset">Basic metadata about the asset being loaded.</param> public bool CanIntercept(IAssetInfo asset) { - MethodInfo canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo? canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic); if (canIntercept == null) throw new InvalidOperationException($"SMAPI couldn't access the {nameof(AssetInterceptorChange)}.{nameof(this.CanInterceptImpl)} implementation."); - return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset }); + return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset })!; } diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index 73e60e24..b6cdec27 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -15,7 +13,7 @@ namespace StardewModdingAPI.Framework.Content public IModMetadata Mod { get; } /// <summary>The content pack on whose behalf the asset is being loaded, if any.</summary> - public IModMetadata OnBehalfOf { get; } + public IModMetadata? OnBehalfOf { get; } /// <summary>If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</summary> public AssetLoadPriority Priority { get; } @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="priority">If there are multiple loads that apply to the same asset, the priority with which this one should be applied.</param> /// <param name="onBehalfOf">The content pack on whose behalf the asset is being loaded, if any.</param> /// <param name="getData">Load the initial value for an asset.</param> - public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata onBehalfOf, Func<IAssetInfo, object> getData) + public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata? onBehalfOf, Func<IAssetInfo, object> getData) { this.Mod = mod; this.Priority = priority; diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 4e48b08c..92452224 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework private readonly ReaderWriterLockSlim ContentManagerLock = new(); /// <summary>A cache of ordered tilesheet IDs used by vanilla maps.</summary> - private readonly Dictionary<string, TilesheetReference[]> VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<string, TilesheetReference[]?> VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase); /// <summary>An unmodified content manager which doesn't intercept assets, used to compare asset data.</summary> private readonly LocalizedContentManager VanillaContentManager; @@ -230,7 +228,7 @@ namespace StardewModdingAPI.Framework public void OnAdditionalLanguagesInitialized() { // update locale cache for custom languages, and load it now (since languages added later won't work) - var customLanguages = this.MainContentManager.Load<List<ModLanguage>>("Data/AdditionalLanguages"); + var customLanguages = this.MainContentManager.Load<List<ModLanguage?>>("Data/AdditionalLanguages"); this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages)); _ = this.LocaleCodes.Value; } @@ -303,7 +301,7 @@ namespace StardewModdingAPI.Framework /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param> /// <param name="relativePath">The asset name within the mod folder.</param> /// <returns>Returns whether the asset was parsed successfully.</returns> - public bool TryParseManagedAssetKey(string key, out string contentManagerID, out IAssetName relativePath) + public bool TryParseManagedAssetKey(string key, [NotNullWhen(true)] out string? contentManagerID, [NotNullWhen(true)] out IAssetName? relativePath) { contentManagerID = null; relativePath = null; @@ -333,9 +331,10 @@ namespace StardewModdingAPI.Framework /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param> /// <param name="assetName">The asset name within the mod folder.</param> public bool DoesManagedAssetExist<T>(string contentManagerID, IAssetName assetName) + where T : notnull { // get content manager - IContentManager contentManager = this.ContentManagerLock.InReadLock(() => + IContentManager? contentManager = this.ContentManagerLock.InReadLock(() => this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID) ); if (contentManager == null) @@ -350,9 +349,10 @@ namespace StardewModdingAPI.Framework /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param> /// <param name="relativePath">The asset name within the mod folder.</param> public T LoadManagedAsset<T>(string contentManagerID, IAssetName relativePath) + where T : notnull { // get content manager - IContentManager contentManager = this.ContentManagerLock.InReadLock(() => + IContentManager? contentManager = this.ContentManagerLock.InReadLock(() => this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID) ); if (contentManager == null) @@ -461,6 +461,7 @@ namespace StardewModdingAPI.Framework /// <typeparam name="T">The asset type.</typeparam> /// <param name="info">The asset info to load or edit.</param> public IEnumerable<AssetOperationGroup> GetAssetOperations<T>(IAssetInfo info) + where T : notnull { return this.AssetOperationsByKey.GetOrSet( info.Name, @@ -491,7 +492,7 @@ namespace StardewModdingAPI.Framework { rootPath = PathUtilities.NormalizePath(rootPath); - if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache cache)) + if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache? cache)) this.CaseInsensitivePathCaches[rootPath] = cache = new CaseInsensitivePathCache(rootPath); return cache; @@ -501,9 +502,9 @@ namespace StardewModdingAPI.Framework /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> public TilesheetReference[] GetVanillaTilesheetIds(string assetName) { - if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets)) + if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[]? tilesheets)) { - tilesheets = this.TryLoadVanillaAsset(assetName, out Map map) + tilesheets = this.TryLoadVanillaAsset(assetName, out Map? map) ? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource, sheet.SheetSize, sheet.TileSize)).ToArray() : null; @@ -516,7 +517,7 @@ namespace StardewModdingAPI.Framework /// <summary>Get the locale code which corresponds to a language enum (e.g. <c>fr-FR</c> given <see cref="LocalizedContentManager.LanguageCode.fr"/>).</summary> /// <param name="language">The language enum to search.</param> - public string GetLocaleCode(LocalizedContentManager.LanguageCode language) + public string? GetLocaleCode(LocalizedContentManager.LanguageCode language) { if (language == LocalizedContentManager.LanguageCode.mod && LocalizedContentManager.CurrentModLanguage == null) return null; @@ -535,7 +536,7 @@ namespace StardewModdingAPI.Framework foreach (IContentManager contentManager in this.ContentManagers) contentManager.Dispose(); this.ContentManagers.Clear(); - this.MainContentManager = null; + this.MainContentManager = null!; // instance no longer usable this.ContentManagerLock.Dispose(); } @@ -560,7 +561,8 @@ namespace StardewModdingAPI.Framework /// <typeparam name="T">The type of asset to load.</typeparam> /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> /// <param name="asset">The loaded asset data.</param> - private bool TryLoadVanillaAsset<T>(string assetName, out T asset) + private bool TryLoadVanillaAsset<T>(string assetName, [NotNullWhen(true)] out T? asset) + where T : notnull { try { @@ -576,12 +578,12 @@ namespace StardewModdingAPI.Framework /// <summary>Get the language enums (like <see cref="LocalizedContentManager.LanguageCode.ja"/>) indexed by locale code (like <c>ja-JP</c>).</summary> /// <param name="customLanguages">The custom languages to add to the lookup.</param> - private Dictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes(IEnumerable<ModLanguage> customLanguages) + private Dictionary<string, LocalizedContentManager.LanguageCode> GetLocaleCodes(IEnumerable<ModLanguage?> customLanguages) { var map = new Dictionary<string, LocalizedContentManager.LanguageCode>(StringComparer.OrdinalIgnoreCase); // custom languages - foreach (ModLanguage language in customLanguages) + foreach (ModLanguage? language in customLanguages) { if (!string.IsNullOrWhiteSpace(language?.LanguageCode)) map[language.LanguageCode] = LocalizedContentManager.LanguageCode.mod; @@ -590,7 +592,7 @@ namespace StardewModdingAPI.Framework // vanilla languages (override custom language if they conflict) foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode))) { - string locale = this.GetLocaleCode(code); + string? locale = this.GetLocaleCode(code); if (locale != null) map[locale] = code; } @@ -602,6 +604,7 @@ namespace StardewModdingAPI.Framework /// <typeparam name="T">The asset type.</typeparam> /// <param name="info">The asset info to load or edit.</param> private IEnumerable<AssetOperationGroup> GetAssetOperationsWithoutCache<T>(IAssetInfo info) + where T : notnull { IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index e494559d..6469fea4 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -70,7 +69,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; // managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) return this.Coordinator.DoesManagedAssetExist<T>(contentManagerID, relativePath); // custom asset from a loader @@ -78,7 +77,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); AssetLoadOperation[] loaders = this.GetLoaders<object>(info).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return false; @@ -102,7 +101,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return this.RawLoad<T>(assetName, useCache: true); // get managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset<T>(contentManagerID, relativePath); this.TrackAsset(assetName, managedAsset, useCache); @@ -151,14 +150,15 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Load the initial asset from the registered loaders.</summary> /// <param name="info">The basic asset metadata.</param> /// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns> - private IAssetData ApplyLoader<T>(IAssetInfo info) + private IAssetData? ApplyLoader<T>(IAssetInfo info) + where T : notnull { // find matching loader - AssetLoadOperation loader; + AssetLoadOperation? loader; { AssetLoadOperation[] loaders = this.GetLoaders<T>(info).OrderByDescending(p => p.Priority).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return null; @@ -196,20 +196,21 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="info">The basic asset metadata.</param> /// <param name="asset">The loaded asset.</param> private IAssetData ApplyEditors<T>(IAssetInfo info, IAssetData asset) + where T : notnull { IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection); // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. { Type actualType = asset.Data.GetType(); - Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; + Type? actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary<,>) || actualOpenType == typeof(List<>) || actualType == typeof(Texture2D) || actualType == typeof(Map))) { return (IAssetData)this.GetType() .GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(actualType) - .Invoke(this, new object[] { info, asset }); + .Invoke(this, new object[] { info, asset })!; } } @@ -232,6 +233,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // validate edit + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method if (asset.Data == null) { mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn); @@ -252,6 +254,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <typeparam name="T">The asset type.</typeparam> /// <param name="info">The basic asset metadata.</param> private IEnumerable<AssetLoadOperation> GetLoaders<T>(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations<T>(info) @@ -262,6 +265,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <typeparam name="T">The asset type.</typeparam> /// <param name="info">The basic asset metadata.</param> private IEnumerable<AssetEditOperation> GetEditors<T>(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations<T>(info) @@ -273,7 +277,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="loaders">The asset loaders to apply.</param> /// <param name="error">The error message to show to the user, if the method returns false.</param> /// <returns>Returns true if only one loader will apply, else false.</returns> - private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, out string error) + private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error) { AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray(); if (required.Length <= 1) @@ -299,7 +303,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="onBehalfOf">The content pack on whose behalf the action is being performed.</param> /// <param name="parenthetical">whether to format the label as a parenthetical shown after the mod name like <c> (for the 'X' content pack)</c>, instead of a standalone label like <c>the 'X' content pack</c>.</param> /// <returns>Returns the on-behalf-of label if applicable, else <c>null</c>.</returns> - private string GetOnBehalfOfLabel(IModMetadata onBehalfOf, bool parenthetical = true) + [return: NotNullIfNotNull("onBehalfOf")] + private string? GetOnBehalfOfLabel(IModMetadata? onBehalfOf, bool parenthetical = true) { if (onBehalfOf == null) return null; @@ -315,7 +320,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="data">The loaded asset data.</param> /// <param name="loader">The loader which loaded the asset.</param> /// <returns>Returns whether the asset passed validation checks (after any fixes were applied).</returns> - private bool TryFixAndValidateLoadedAsset<T>(IAssetInfo info, T data, AssetLoadOperation loader) + private bool TryFixAndValidateLoadedAsset<T>(IAssetInfo info, [NotNullWhen(true)] T? data, AssetLoadOperation loader) + where T : notnull { IModMetadata mod = loader.Mod; @@ -335,7 +341,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // add missing tilesheet if (loadedMap.GetTileSheet(vanillaSheet.Id) == null) { - mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); + mod.Monitor!.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize)); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 46d5d24e..1b0e1016 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Globalization; using Microsoft.Xna.Framework.Graphics; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Get whether a texture was loaded by this content manager.</summary> /// <param name="texture">The texture to check.</param> - public bool IsResponsibleFor(Texture2D texture) + public bool IsResponsibleFor(Texture2D? texture) { return texture?.Tag is string tag diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index c8b2ae64..ac67cad5 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Content; @@ -33,25 +31,28 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Get whether an asset exists and can be loaded.</summary> /// <typeparam name="T">The expected asset type.</typeparam> /// <param name="assetName">The normalized asset name.</param> - bool DoesAssetExist<T>(IAssetName assetName); + bool DoesAssetExist<T>(IAssetName assetName) + where T: notnull; /// <summary>Load an asset through the content pipeline, using a localized variant of the <paramref name="assetName"/> if available.</summary> /// <typeparam name="T">The type of asset to load.</typeparam> /// <param name="assetName">The asset name relative to the loader root directory.</param> /// <param name="language">The language for which to load the asset.</param> /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param> - T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); + T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache) + where T : notnull; /// <summary>Load an asset through the content pipeline, using the exact asset name without checking for localized variants.</summary> /// <typeparam name="T">The type of asset to load.</typeparam> /// <param name="assetName">The asset name relative to the loader root directory.</param> /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param> - T LoadExact<T>(IAssetName assetName, bool useCache); + T LoadExact<T>(IAssetName assetName, bool useCache) + where T : notnull; /// <summary>Assert that the given key has a valid format and return a normalized form consistent with the underlying cache.</summary> /// <param name="assetName">The asset key to check.</param> /// <exception cref="SContentLoadException">The asset key is empty or contains invalid characters.</exception> - string AssertAndNormalizeAssetName(string assetName); + string AssertAndNormalizeAssetName(string? assetName); /// <summary>Get the current content locale.</summary> string GetLocale(); diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 2d33a22e..2cfd5cce 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; @@ -69,12 +67,12 @@ namespace StardewModdingAPI.Framework } /// <inheritdoc /> - public TModel ReadJsonFile<TModel>(string path) where TModel : class + public TModel? ReadJsonFile<TModel>(string path) where TModel : class { path = PathUtilities.NormalizePath(path); FileInfo file = this.GetFile(path); - return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel model) + return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel? model) ? model : null; } @@ -93,6 +91,7 @@ namespace StardewModdingAPI.Framework /// <inheritdoc /> [Obsolete] public T LoadAsset<T>(string key) + where T : notnull { return this.ModContent.Load<T>(key); } @@ -101,7 +100,7 @@ namespace StardewModdingAPI.Framework [Obsolete] public string GetActualAssetKey(string key) { - return this.ModContent.GetInternalAssetName(key)?.Name; + return this.ModContent.GetInternalAssetName(key).Name; } diff --git a/src/SMAPI/Framework/CursorPosition.cs b/src/SMAPI/Framework/CursorPosition.cs index 8f36a554..24084830 100644 --- a/src/SMAPI/Framework/CursorPosition.cs +++ b/src/SMAPI/Framework/CursorPosition.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.Xna.Framework; using StardewValley; @@ -41,7 +39,7 @@ namespace StardewModdingAPI.Framework } /// <inheritdoc /> - public bool Equals(ICursorPosition other) + public bool Equals(ICursorPosition? other) { return other != null && this.AbsolutePixels == other.AbsolutePixels; } diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 8fa31165..4b8a770d 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.Events private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new(); /// <summary>A cached snapshot of <see cref="Handlers"/>, or <c>null</c> to rebuild it next raise.</summary> - private ManagedEventHandler<TEventArgs>[] CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>(); + private ManagedEventHandler<TEventArgs>[]? CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>(); /// <summary>The total number of event handlers registered for this events, regardless of whether they're still registered.</summary> private int RegistrationIndex; @@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Raise the event and notify all handlers.</summary> /// <param name="args">The event arguments to pass.</param> /// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param> - public void Raise(TEventArgs args, Func<IModMetadata, bool> match = null) + public void Raise(TEventArgs args, Func<IModMetadata, bool>? match = null) { this.Raise((_, invoke) => invoke(args), match); } @@ -108,7 +106,7 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Raise the event and notify all handlers.</summary> /// <param name="invoke">Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it.</param> /// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param> - public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool> match = null) + public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool>? match = null) { // skip if no handlers if (this.Handlers.Count == 0) diff --git a/src/SMAPI/Framework/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs index f31bc04d..d32acdb9 100644 --- a/src/SMAPI/Framework/Events/ManagedEventHandler.cs +++ b/src/SMAPI/Framework/Events/ManagedEventHandler.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.Events } /// <inheritdoc /> - public int CompareTo(object obj) + public int CompareTo(object? obj) { if (obj is not ManagedEventHandler<TEventArgs> other) throw new ArgumentException("Can't compare to an unrelated object type."); diff --git a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs index c21a6b0e..be1fe748 100644 --- a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs +++ b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; @@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Exceptions /// <summary>Construct an instance.</summary> /// <param name="message">The error message.</param> /// <param name="ex">The underlying exception, if any.</param> - public SContentLoadException(string message, Exception ex = null) + public SContentLoadException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI/Framework/GameVersion.cs b/src/SMAPI/Framework/GameVersion.cs index aa91d8f3..542c1345 100644 --- a/src/SMAPI/Framework/GameVersion.cs +++ b/src/SMAPI/Framework/GameVersion.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework private static string GetSemanticVersionString(string gameVersion) { // mapped version - return GameVersion.VersionMap.TryGetValue(gameVersion, out string semanticVersion) + return GameVersion.VersionMap.TryGetValue(gameVersion, out string? semanticVersion) ? semanticVersion : gameVersion; } @@ -64,10 +62,10 @@ namespace StardewModdingAPI.Framework /// <param name="semanticVersion">The semantic version string.</param> private static string GetGameVersionString(string semanticVersion) { - foreach (var mapping in GameVersion.VersionMap) + foreach ((string gameVersion, string equivalentSemanticVersion) in GameVersion.VersionMap) { - if (mapping.Value.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase)) - return mapping.Key; + if (equivalentSemanticVersion.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase)) + return gameVersion; } return semanticVersion; diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index 800b198a..7cee20b9 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using StardewModdingAPI.Framework.ModHelpers; @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework string RelativeDirectoryPath { get; } /// <summary>Metadata about the mod from SMAPI's internal data (if any).</summary> - ModDataRecordVersionedFields DataRecord { get; } + ModDataRecordVersionedFields? DataRecord { get; } /// <summary>The metadata resolution status.</summary> ModMetadataStatus Status { get; } @@ -41,31 +39,31 @@ namespace StardewModdingAPI.Framework ModWarning Warnings { get; } /// <summary>The reason the metadata is invalid, if any.</summary> - string Error { get; } + string? Error { get; } /// <summary>A detailed technical message for <see cref="Error"/>, if any.</summary> - public string ErrorDetails { get; } + string? ErrorDetails { get; } /// <summary>Whether the mod folder should be ignored. This is <c>true</c> if it was found within a folder whose name starts with a dot.</summary> bool IsIgnored { get; } /// <summary>The mod instance (if loaded and <see cref="IModInfo.IsContentPack"/> is false).</summary> - IMod Mod { get; } + IMod? Mod { get; } /// <summary>The content pack instance (if loaded and <see cref="IModInfo.IsContentPack"/> is true).</summary> - IContentPack ContentPack { get; } + IContentPack? ContentPack { get; } /// <summary>The translations for this mod (if loaded).</summary> - TranslationHelper Translations { get; } + TranslationHelper? Translations { get; } /// <summary>Writes messages to the console and log file as this mod.</summary> - IMonitor Monitor { get; } + IMonitor? Monitor { get; } /// <summary>The mod-provided API (if any).</summary> - object Api { get; } + object? Api { get; } /// <summary>The update-check metadata for this mod (if any).</summary> - ModEntryModel UpdateCheckData { get; } + ModEntryModel? UpdateCheckData { get; } /// <summary>The fake content packs created by this mod, if any.</summary> ISet<WeakReference<ContentPack>> FakeContentPacks { get; } @@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework /// <param name="error">The reason the metadata is invalid, if any.</param> /// <param name="errorDetails">A detailed technical message, if any.</param> /// <returns>Return the instance for chaining.</returns> - IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null); + IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null); /// <summary>Set a warning flag for the mod.</summary> /// <param name="warning">The warning to set.</param> @@ -103,7 +101,7 @@ namespace StardewModdingAPI.Framework /// <summary>Set the mod-provided API instance.</summary> /// <param name="api">The mod-provided API.</param> - IModMetadata SetApi(object api); + IModMetadata SetApi(object? api); /// <summary>Set the update-check metadata for this mod.</summary> /// <param name="data">The update-check metadata.</param> @@ -117,7 +115,7 @@ namespace StardewModdingAPI.Framework /// <summary>Whether the mod has the given ID.</summary> /// <param name="id">The mod ID to check.</param> - bool HasID(string id); + bool HasID(string? id); /// <summary>Get the defined update keys.</summary> /// <param name="validOnly">Only return valid update keys.</param> diff --git a/src/SMAPI/Framework/Logging/LogFileManager.cs b/src/SMAPI/Framework/Logging/LogFileManager.cs index 0b6f9ad2..b396091a 100644 --- a/src/SMAPI/Framework/Logging/LogFileManager.cs +++ b/src/SMAPI/Framework/Logging/LogFileManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Logging this.Path = path; // create log directory if needed - string logDir = System.IO.Path.GetDirectoryName(path); + string? logDir = System.IO.Path.GetDirectoryName(path); if (logDir == null) throw new ArgumentException($"The log path '{path}' is not valid."); Directory.CreateDirectory(logDir); diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 12ef0439..b610b395 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -114,6 +112,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public T Load<T>(string key, ContentSource source = ContentSource.ModFolder) + where T : notnull { IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent); @@ -140,7 +139,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> [Pure] - public string NormalizeAssetName(string assetName) + public string NormalizeAssetName(string? assetName) { return this.ModContentManager.AssertAndNormalizeAssetName(assetName); } @@ -171,6 +170,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public bool InvalidateCache<T>() + where T : notnull { this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible."); return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any(); @@ -184,7 +184,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public IAssetData GetPatchHelper<T>(T data, string assetName = null) + public IAssetData GetPatchHelper<T>(T data, string? assetName = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 86a34ee8..92b3b398 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -42,19 +40,21 @@ namespace StardewModdingAPI.Framework.ModHelpers ** JSON file ****/ /// <inheritdoc /> - public TModel ReadJsonFile<TModel>(string path) where TModel : class + public TModel? ReadJsonFile<TModel>(string path) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.ReadJsonFile)} with a relative path."); path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path)); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data) ? data : null; } /// <inheritdoc /> - public void WriteJsonFile<TModel>(string path, TModel data) where TModel : class + public void WriteJsonFile<TModel>(string path, TModel? data) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); @@ -71,7 +71,8 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Save file ****/ /// <inheritdoc /> - public TModel ReadSaveData<TModel>(string key) where TModel : class + public TModel? ReadSaveData<TModel>(string key) + where TModel : class { if (Context.LoadStage == LoadStage.None) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} when a save file isn't loaded."); @@ -82,14 +83,15 @@ namespace StardewModdingAPI.Framework.ModHelpers string internalKey = this.GetSaveFileKey(key); foreach (IDictionary<string, string> dataField in this.GetDataFields(Context.LoadStage)) { - if (dataField.TryGetValue(internalKey, out string value)) + if (dataField.TryGetValue(internalKey, out string? value)) return this.JsonHelper.Deserialize<TModel>(value); } return null; } /// <inheritdoc /> - public void WriteSaveData<TModel>(string key, TModel model) where TModel : class + public void WriteSaveData<TModel>(string key, TModel? model) + where TModel : class { if (Context.LoadStage == LoadStage.None) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when a save file isn't loaded."); @@ -97,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when connected to a remote host. (Save files are stored on the main player's computer.)"); string internalKey = this.GetSaveFileKey(key); - string data = model != null + string? data = model != null ? this.JsonHelper.Serialize(model, Formatting.None) : null; @@ -114,16 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Global app data ****/ /// <inheritdoc /> - public TModel ReadGlobalData<TModel>(string key) where TModel : class + public TModel? ReadGlobalData<TModel>(string key) + where TModel : class { string path = this.GetGlobalDataPath(key); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data) ? data : null; } /// <inheritdoc /> - public void WriteGlobalData<TModel>(string key, TModel data) where TModel : class + public void WriteGlobalData<TModel>(string key, TModel? data) + where TModel : class { string path = this.GetGlobalDataPath(key); if (data != null) diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 6d0c2f5f..4c1cde02 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Linq; using StardewModdingAPI.Framework.Content; @@ -71,6 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public T Load<T>(string key) + where T : notnull { IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: true); return this.Load<T>(assetName); @@ -78,6 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public T Load<T>(IAssetName assetName) + where T : notnull { try { @@ -105,6 +105,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public bool InvalidateCache<T>() + where T : notnull { this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible."); return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any(); @@ -118,7 +119,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public IAssetData GetPatchHelper<T>(T data, string assetName = null) + public IAssetData GetPatchHelper<T>(T data, string? assetName = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index b149ed82..fc47f269 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; @@ -57,6 +55,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public T Load<T>(string relativePath) + where T : notnull { relativePath = this.RelativePathCache.GetAssetName(relativePath); @@ -80,7 +79,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public IAssetData GetPatchHelper<T>(T data, string relativePath = null) + public IAssetData GetPatchHelper<T>(T data, string? relativePath = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index e277e6fa..84899610 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.Reflection; @@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public IModInfo Get(string uniqueID) + public IModInfo? Get(string uniqueID) { return this.Registry.Get(uniqueID); } @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public object GetApi(string uniqueID) + public object? GetApi(string uniqueID) { // validate ready if (!this.Registry.AreAllModsInitialized) @@ -69,17 +67,18 @@ namespace StardewModdingAPI.Framework.ModHelpers } // get raw API - IModMetadata mod = this.Registry.Get(uniqueID); + IModMetadata? mod = this.Registry.Get(uniqueID); if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID)) this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}."); return mod?.Api; } /// <inheritdoc /> - public TInterface GetApi<TInterface>(string uniqueID) where TInterface : class + public TInterface? GetApi<TInterface>(string uniqueID) + where TInterface : class { // get raw API - object api = this.GetApi(uniqueID); + object? api = this.GetApi(uniqueID); if (api == null) return null; diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index 96b074e2..a419327e 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.Networking; using StardewValley; @@ -41,9 +39,9 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public IMultiplayerPeer GetConnectedPlayer(long id) + public IMultiplayerPeer? GetConnectedPlayer(long id) { - return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer peer) + return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer? peer) ? peer : null; } @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// <inheritdoc /> - public void SendMessage<TMessage>(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null) + public void SendMessage<TMessage>(TMessage message, string messageType, string[]? modIDs = null, long[]? playerIDs = null) { this.Multiplayer.BroadcastModMessage( message: message, diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 24cbd01c..af018c87 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; using StardewModdingAPI.Framework.Reflection; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetField<TValue>(obj, name, required) - ); + )!; } /// <inheritdoc /> @@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetField<TValue>(type, name, required) - ); + )!; } /// <inheritdoc /> @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetProperty<TValue>(obj, name, required) - ); + )!; } /// <inheritdoc /> @@ -63,7 +61,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetProperty<TValue>(type, name, required) - ); + )!; } /// <inheritdoc /> @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetMethod(obj, name, required) - ); + )!; } /// <inheritdoc /> @@ -79,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetMethod(type, name, required) - ); + )!; } @@ -90,7 +88,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <typeparam name="T">The field value type.</typeparam> /// <param name="field">The field being accessed.</param> /// <returns>Returns the same field instance for convenience.</returns> - private IReflectedField<T> AssertAccessAllowed<T>(IReflectedField<T> field) + private IReflectedField<T>? AssertAccessAllowed<T>(IReflectedField<T>? field) { this.AssertAccessAllowed(field?.FieldInfo); return field; @@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <typeparam name="T">The property value type.</typeparam> /// <param name="property">The property being accessed.</param> /// <returns>Returns the same property instance for convenience.</returns> - private IReflectedProperty<T> AssertAccessAllowed<T>(IReflectedProperty<T> property) + private IReflectedProperty<T>? AssertAccessAllowed<T>(IReflectedProperty<T>? property) { this.AssertAccessAllowed(property?.PropertyInfo.GetMethod?.GetBaseDefinition()); this.AssertAccessAllowed(property?.PropertyInfo.SetMethod?.GetBaseDefinition()); @@ -110,7 +108,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>Assert that mods can use the reflection helper to access the given member.</summary> /// <param name="method">The method being accessed.</param> /// <returns>Returns the same method instance for convenience.</returns> - private IReflectedMethod AssertAccessAllowed(IReflectedMethod method) + private IReflectedMethod? AssertAccessAllowed(IReflectedMethod? method) { this.AssertAccessAllowed(method?.MethodInfo.GetBaseDefinition()); return method; @@ -118,18 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>Assert that mods can use the reflection helper to access the given member.</summary> /// <param name="member">The member being accessed.</param> - private void AssertAccessAllowed(MemberInfo member) + private void AssertAccessAllowed(MemberInfo? member) { if (member == null) return; // get type which defines the member - Type declaringType = member.DeclaringType; + Type? declaringType = member.DeclaringType; if (declaringType == null) throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen // validate access - string rootNamespace = typeof(Program).Namespace; + string? rootNamespace = typeof(Program).Namespace; if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true) throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)"); } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 1d4ddf72..b3378ad1 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Mono.Cecil; @@ -38,6 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Resolve an assembly reference.</summary> /// <param name="name">The assembly name.</param> + /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception> public override AssemblyDefinition Resolve(AssemblyNameReference name) { return this.ResolveName(name.Name) ?? base.Resolve(name); @@ -46,6 +45,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Resolve an assembly reference.</summary> /// <param name="name">The assembly name.</param> /// <param name="parameters">The assembly reader parameters.</param> + /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception> public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { return this.ResolveName(name.Name) ?? base.Resolve(name, parameters); @@ -57,9 +57,9 @@ namespace StardewModdingAPI.Framework.ModLoading *********/ /// <summary>Resolve a known assembly definition based on its short or full name.</summary> /// <param name="name">The assembly's short or full name.</param> - private AssemblyDefinition ResolveName(string name) + private AssemblyDefinition? ResolveName(string name) { - return this.Lookup.TryGetValue(name, out AssemblyDefinition match) + return this.Lookup.TryGetValue(name, out AssemblyDefinition? match) ? match : null; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 7c94beb7..f5d449c5 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -57,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.MethodNames.Any()) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name)) { string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1]; diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 96b4098a..7fe4abec 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -51,7 +49,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.FieldNames.Any()) { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && fieldRef.DeclaringType.FullName == this.FullTypeName && this.FieldNames.Contains(fieldRef.Name)) { this.FieldNames.Remove(fieldRef.Name); diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 7d3c1fd7..e8fdc8c7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="instruction">The IL instruction.</param> protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index b2f2e193..2af76f55 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="instruction">The IL instruction.</param> protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 81f90498..f34542c3 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -34,11 +33,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { // get target field - FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); + FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (targetField == null) return false; @@ -51,16 +50,16 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodReference = RewriteHelper.AsMethodReference(instruction); if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType)) { // get potential targets - MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); + MethodDefinition[]? candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); if (candidateMethods == null || !candidateMethods.Any()) return false; // compare return types - MethodDefinition methodDef = methodReference.Resolve(); + MethodDefinition? methodDef = methodReference.Resolve(); if (methodDef == null) return false; // validated by ReferenceToMissingMemberFinder @@ -80,7 +79,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index 001d1986..fae7fb12 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -33,10 +32,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { - FieldDefinition target = fieldRef.Resolve(); + FieldDefinition? target = fieldRef.Resolve(); if (target == null || target.HasConstant) { this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)"); @@ -45,10 +44,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && this.ShouldValidate(methodRef.DeclaringType) && !this.IsUnsupported(methodRef)) { - MethodDefinition target = methodRef.Resolve(); + MethodDefinition? target = methodRef.Resolve(); if (target == null) { string phrase; @@ -73,7 +72,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 4c589ed8..17acbf9a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="assemblyName">The full assembly name to which to find references.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : base(defaultPhrase: $"{assemblyName} assembly") { this.AssemblyName = assemblyName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 04a5b970..77762f41 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Mono.Cecil; @@ -20,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -30,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="fullTypeNames">The full type names to match.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : base(defaultPhrase: $"{string.Join(", ", fullTypeNames)} type{(fullTypeNames.Length != 1 ? "s" : "")}") // default phrase should never be used { this.FullTypeNames = new HashSet<string>(fullTypeNames); @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// <param name="fullTypeName">The full type name to match.</param> /// <param name="result">The result to return for matching instructions.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool> shouldIgnore = null) + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func<TypeReference, bool>? shouldIgnore = null) : this(new[] { fullTypeName }, result, shouldIgnore) { } /// <inheritdoc /> diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index bea786cd..865bf076 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Mono.Cecil; @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <param name="flag">The result flag to set.</param> /// <param name="resultMessage">The result message to add.</param> /// <returns>Returns true for convenience.</returns> - protected bool MarkFlag(InstructionHandleResult flag, string resultMessage = null) + protected bool MarkFlag(InstructionHandleResult flag, string? resultMessage = null) { this.Flags.Add(flag); if (resultMessage != null) diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 09ff78f7..55369602 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -10,6 +9,7 @@ using Mono.Collections.Generic; namespace StardewModdingAPI.Framework.ModLoading.Framework { /// <summary>Handles recursively rewriting loaded assembly code.</summary> + [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "Rewrite callbacks are invoked immediately.")] internal class RecursiveRewriter { /********* @@ -77,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework { changed |= this.RewriteModuleImpl(this.Module); - foreach (var type in types) + foreach (TypeDefinition type in types) changed |= this.RewriteTypeDefinition(type); } catch (Exception ex) @@ -129,9 +129,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework ILProcessor cil = method.Body.GetILProcessor(); Collection<Instruction> instructions = cil.Body.Instructions; bool addedInstructions = false; + // ReSharper disable once ForCanBeConvertedToForeach -- deliberate to allow changing the collection for (int i = 0; i < instructions.Count; i++) { - var instruction = instructions[i]; + Instruction instruction = instructions[i]; if (instruction.OpCode.Code == Code.Nop) continue; @@ -174,7 +175,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool rewritten = false; // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { rewritten |= this.RewriteTypeReference(fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); @@ -182,7 +183,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null) this.RewriteMethodReference(methodRef); @@ -212,7 +213,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework }); rewritten |= this.RewriteTypeReference(methodRef.ReturnType, newType => methodRef.ReturnType = newType); - foreach (var parameter in methodRef.Parameters) + foreach (ParameterDefinition parameter in methodRef.Parameters) rewritten |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); if (methodRef is GenericInstanceMethod genericRef) @@ -264,7 +265,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool curChanged = false; // attribute type - TypeReference newAttrType = null; + TypeReference? newAttrType = null; rewritten |= this.RewriteTypeReference(attribute.AttributeType, newType => { newAttrType = newType; @@ -289,9 +290,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework if (curChanged) { // get constructor - MethodDefinition constructor = (newAttrType ?? attribute.AttributeType) + MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType) .Resolve() - .Methods + ?.Methods .Where(method => method.IsConstructor) .FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor)); if (constructor == null) @@ -301,9 +302,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework var newAttr = new CustomAttribute(this.Module.ImportReference(constructor)); for (int i = 0; i < argTypes.Length; i++) newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value)); - foreach (var prop in attribute.Properties) + foreach (CustomAttributeNamedArgument prop in attribute.Properties) newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument)); - foreach (var field in attribute.Fields) + foreach (CustomAttributeNamedArgument field in attribute.Fields) newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument)); // swap attribute diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 8f47fbdd..15f71251 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Linq; using System.Reflection; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework *********/ /// <summary>Get the field reference from an instruction if it matches.</summary> /// <param name="instruction">The IL instruction.</param> - public static FieldReference AsFieldReference(Instruction instruction) + public static FieldReference? AsFieldReference(Instruction instruction) { return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld ? (FieldReference)instruction.Operand @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <summary>Get the method reference from an instruction if it matches.</summary> /// <param name="instruction">The IL instruction.</param> - public static MethodReference AsMethodReference(Instruction instruction) + public static MethodReference? AsMethodReference(Instruction instruction) { return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj ? (MethodReference)instruction.Operand @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <summary>Get the CIL instruction to load a value onto the stack.</summary> /// <param name="rawValue">The constant value to inject.</param> /// <returns>Returns the instruction, or <c>null</c> if the value type isn't supported.</returns> - public static Instruction GetLoadValueInstruction(object rawValue) + public static Instruction? GetLoadValueInstruction(object? rawValue) { return rawValue switch { @@ -151,7 +149,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// <param name="typeA">The type ID to compare.</param> /// <param name="typeB">The other type ID to compare.</param> /// <returns>true if the type IDs look like the same type, false if not.</returns> - public static bool LooksLikeSameType(TypeReference typeA, TypeReference typeB) + public static bool LooksLikeSameType(TypeReference? typeA, TypeReference? typeB) { return RewriteHelper.TypeDefinitionComparer.Equals(typeA, typeB); } diff --git a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs index 9dca9bc4..b53a9886 100644 --- a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs +++ b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.ModLoading @@ -10,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Construct an instance.</summary> /// <param name="message">The error message.</param> /// <param name="ex">The underlying exception, if any.</param> - public InvalidModStateException(string message, Exception ex = null) + public InvalidModStateException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 0e698bfd..fe54634b 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using StardewModdingAPI.Framework.ModHelpers; @@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading public IManifest Manifest { get; } /// <inheritdoc /> - public ModDataRecordVersionedFields DataRecord { get; } + public ModDataRecordVersionedFields? DataRecord { get; } /// <inheritdoc /> public ModMetadataStatus Status { get; private set; } @@ -56,33 +55,35 @@ namespace StardewModdingAPI.Framework.ModLoading public ModWarning Warnings => this.ActualWarnings & ~(this.DataRecord?.DataRecord.SuppressWarnings ?? ModWarning.None); /// <inheritdoc /> - public string Error { get; private set; } + public string? Error { get; private set; } /// <inheritdoc /> - public string ErrorDetails { get; private set; } + public string? ErrorDetails { get; private set; } /// <inheritdoc /> public bool IsIgnored { get; } /// <inheritdoc /> - public IMod Mod { get; private set; } + public IMod? Mod { get; private set; } /// <inheritdoc /> - public IContentPack ContentPack { get; private set; } + public IContentPack? ContentPack { get; private set; } /// <inheritdoc /> - public TranslationHelper Translations { get; private set; } + public TranslationHelper? Translations { get; private set; } /// <inheritdoc /> - public IMonitor Monitor { get; private set; } + public IMonitor? Monitor { get; private set; } /// <inheritdoc /> - public object Api { get; private set; } + public object? Api { get; private set; } /// <inheritdoc /> - public ModEntryModel UpdateCheckData { get; private set; } + public ModEntryModel? UpdateCheckData { get; private set; } /// <inheritdoc /> + [MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))] + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")] public bool IsContentPack => this.Manifest?.ContentPackFor != null; /// <summary>The fake content packs created by this mod, if any.</summary> @@ -99,13 +100,13 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="manifest">The mod manifest.</param> /// <param name="dataRecord">Metadata about the mod from SMAPI's internal data (if any).</param> /// <param name="isIgnored">Whether the mod folder should be ignored. This should be <c>true</c> if it was found within a folder whose name starts with a dot.</param> - public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored) + public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest? manifest, ModDataRecordVersionedFields? dataRecord, bool isIgnored) { this.DisplayName = displayName; this.DirectoryPath = directoryPath; this.RootPath = rootPath; this.RelativeDirectoryPath = PathUtilities.GetRelativePath(this.RootPath, this.DirectoryPath); - this.Manifest = manifest; + this.Manifest = manifest!; // manifest may be null in low-level SMAPI code, but won't be null once it's received by mods via IModInfo this.DataRecord = dataRecord; this.IsIgnored = isIgnored; @@ -121,7 +122,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null) + public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null) { this.Status = status; this.FailReason = reason; @@ -162,7 +163,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public IModMetadata SetApi(object api) + public IModMetadata SetApi(object? api) { this.Api = api; return this; @@ -176,6 +177,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> + [MemberNotNullWhen(true, nameof(IModInfo.Manifest))] public bool HasManifest() { return this.Manifest != null; @@ -190,7 +192,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// <inheritdoc /> - public bool HasID(string id) + public bool HasID(string? id) { return this.HasID() @@ -245,7 +247,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <inheritdoc /> public string GetRelativePathWithRoot() { - string rootFolderName = Path.GetFileName(this.RootPath) ?? ""; + string rootFolderName = Path.GetFileName(this.RootPath); return Path.Combine(rootFolderName, this.RelativeDirectoryPath); } @@ -254,7 +256,7 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (var reference in this.FakeContentPacks.ToArray()) { - if (!reference.TryGetTarget(out ContentPack pack)) + if (!reference.TryGetTarget(out ContentPack? pack)) { this.FakeContentPacks.Remove(reference); continue; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index c05005b8..afe38bfd 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /********* ** Public methods *********/ - public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) + public static ConstructorInfo DeclaredConstructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); } - public static ConstructorInfo Constructor(Type type, Type[] parameters = null) + public static ConstructorInfo Constructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index fea8c100..9c8ba2b0 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -30,7 +28,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades return new Harmony(id); } - public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")] + public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { // In Harmony 1.x you could target a virtual method that's not implemented by the // target type, but in Harmony 2.0 you need to target the concrete implementation. @@ -60,7 +59,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// <param name="prefix">The prefix method, if any.</param> /// <param name="postfix">The postfix method, if any.</param> /// <param name="transpiler">The transpiler method, if any.</param> - private string GetPatchTypesLabel(HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + private string GetPatchTypesLabel(HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { var patchTypes = new List<string>(); @@ -76,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// <summary>Get a human-readable label for the method being patched.</summary> /// <param name="method">The method being patched.</param> - private string GetMethodLabel(MethodBase method) + private string GetMethodLabel(MethodBase? method) { return method != null ? $"method {method.DeclaringType?.FullName}.{method.Name}" diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 93124591..2b1ca54b 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades this.ImportMethodImpl(method); } - public HarmonyMethodFacade(Type type, string name, Type[] parameters = null) + public HarmonyMethodFacade(Type type, string name, Type[]? parameters = null) { this.ImportMethodImpl(AccessTools.Method(type, name, parameters)); } @@ -40,7 +38,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades // internal code still handles null fine. For backwards compatibility, this bypasses // the new restriction when the mod hasn't been updated for Harmony 2.0 yet. - MethodInfo importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo? importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); if (importMethod == null) throw new InvalidOperationException("Can't find 'HarmonyMethod.ImportMethod' method"); importMethod.Invoke(this, new object[] { methodInfo }); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs index 92397c58..aea490c8 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using HarmonyLib; using Mono.Cecil; @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (this.ShouldRewrite) { // rewrite Harmony 1.x methods to Harmony 2.0 - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (this.TryRewriteMethodsToFacade(module, methodRef)) { this.OnChanged(); @@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } // rewrite renamed fields - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy") @@ -95,13 +93,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <summary>Rewrite methods to use Harmony facades if needed.</summary> /// <param name="module">The assembly module containing the method reference.</param> /// <param name="methodRef">The method reference to map.</param> - private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference methodRef) + private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference? methodRef) { if (!this.ReplacedTypes) return false; // not Harmony (or already using Harmony 2.0) // get facade type - Type toType = methodRef?.DeclaringType.FullName switch + Type? toType = methodRef?.DeclaringType.FullName switch { "HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade), "HarmonyLib.AccessTools" => typeof(AccessToolsFacade), @@ -112,9 +110,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // map if there's a matching method - if (RewriteHelper.HasMatchingSignature(toType, methodRef)) + if (RewriteHelper.HasMatchingSignature(toType, methodRef!)) { - methodRef.DeclaringType = module.ImportReference(toType); + methodRef!.DeclaringType = module.ImportReference(toType); return true; } @@ -139,7 +137,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters { string fullName = type.FullName.Replace("Harmony.", "HarmonyLib."); string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName); - return Type.GetType(targetName, throwOnError: true); + return Type.GetType(targetName, throwOnError: true)!; } } } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index fc06e779..9c6a3980 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -33,17 +32,17 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field ref - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType)) return false; // skip if not broken - FieldDefinition fieldDefinition = fieldRef.Resolve(); + FieldDefinition? fieldDefinition = fieldRef.Resolve(); if (fieldDefinition?.HasConstant == false) return false; // rewrite if possible - TypeDefinition declaringType = fieldRef.DeclaringType.Resolve(); + TypeDefinition? declaringType = fieldRef.DeclaringType.Resolve(); bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld; return this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead) @@ -56,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } @@ -70,8 +69,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) { // get equivalent property - PropertyDefinition property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); - MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; + PropertyDefinition? property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + MethodDefinition? method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; @@ -86,14 +85,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <summary>Try rewriting the field into a matching const field.</summary> /// <param name="instruction">The CIL instruction to rewrite.</param> /// <param name="field">The field definition.</param> - private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field) + private bool TryRewriteToConstField(Instruction instruction, FieldDefinition? field) { // must have been a static field read, and the new field must be const if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true) return false; // get opcode for value type - Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); + Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); if (loadInstruction == null) return false; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 4860072c..601ecbbc 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -33,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) return false; @@ -42,13 +41,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // get type - var type = methodRef.DeclaringType.Resolve(); + TypeDefinition? type = methodRef.DeclaringType.Resolve(); if (type == null) return false; // get method definition - MethodDefinition method = null; - foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + MethodDefinition? method = null; + foreach (MethodDefinition match in type.Methods.Where(p => p.Name == methodRef.Name)) { // reference matches initial parameters of definition if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) @@ -72,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized // rewrite method reference - foreach (Instruction loadInstruction in loadInstructions) + foreach (Instruction? loadInstruction in loadInstructions) cil.InsertBefore(instruction, loadInstruction); instruction.Operand = module.ImportReference(method); @@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Whether references to the given type should be validated.</summary> /// <param name="type">The type reference.</param> - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 00daf337..2e2f6316 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -28,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromType">The type whose methods to remap.</param> /// <param name="toType">The type with methods to map to.</param> /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param> - public MethodParentRewriter(string fromType, Type toType, string nounPhrase = null) + public MethodParentRewriter(string fromType, Type toType, string? nounPhrase = null) : base(nounPhrase ?? $"{fromType.Split('.').Last()} methods") { this.FromType = fromType; @@ -39,14 +38,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromType">The type whose methods to remap.</param> /// <param name="toType">The type with methods to map to.</param> /// <param name="nounPhrase">A brief noun phrase indicating what the instruction finder matches (or <c>null</c> to generate one).</param> - public MethodParentRewriter(Type fromType, Type toType, string nounPhrase = null) - : this(fromType.FullName, toType, nounPhrase) { } + public MethodParentRewriter(Type fromType, Type toType, string? nounPhrase = null) + : this(fromType.FullName!, toType, nounPhrase) { } /// <inheritdoc /> public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (!this.IsMatch(methodRef)) return false; @@ -61,7 +60,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// <summary>Get whether a CIL instruction matches.</summary> /// <param name="methodRef">The method reference.</param> - private bool IsMatch(MethodReference methodRef) + private bool IsMatch([NotNullWhen(true)] MethodReference? methodRef) { return methodRef != null diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index bdc4c4f3..a81cb5be 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private readonly Type ToType; /// <summary>Get whether a matched type should be ignored.</summary> - private readonly Func<TypeReference, bool> ShouldIgnore; + private readonly Func<TypeReference, bool>? ShouldIgnore; /********* @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// <param name="fromTypeFullName">The full type name to which to find references.</param> /// <param name="toType">The new type to reference.</param> /// <param name="shouldIgnore">Get whether a matched type should be ignored.</param> - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool> shouldIgnore = null) + public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func<TypeReference, bool>? shouldIgnore = null) : base($"{fromTypeFullName} type") { this.FromTypeName = fromTypeFullName; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs index 4af7c1e7..0d3aff9f 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -38,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// <param name="fileName">The assembly file name.</param> public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { - return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, fileName); } @@ -48,7 +46,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// <param name="symbolStream">The loaded symbol file stream.</param> public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream) { - return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, symbolStream); } diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index 248c29fc..d81d763e 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <c>TKey</c> in the above example). If all components are equal after substitution, and the /// tokens can all be mapped to the same generic type, the types are considered equal. /// </remarks> - internal class TypeReferenceComparer : IEqualityComparer<TypeReference> + internal class TypeReferenceComparer : IEqualityComparer<TypeReference?> { /********* ** Public methods @@ -26,7 +24,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <summary>Get whether the specified objects are equal.</summary> /// <param name="a">The first object to compare.</param> /// <param name="b">The second object to compare.</param> - public bool Equals(TypeReference a, TypeReference b) + public bool Equals(TypeReference? a, TypeReference? b) { if (a == null || b == null) return a == b; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="typeB">The second type to compare.</param> private bool HeuristicallyEquals(TypeReference typeA, TypeReference typeB) { - bool HeuristicallyEquals(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap) + bool HeuristicallyEqualsImpl(string typeNameA, string typeNameB, IDictionary<string, string> tokenMap) { // analyze type names bool hasTokensA = typeNameA.Contains("!"); @@ -82,14 +80,14 @@ namespace StardewModdingAPI.Framework.ModLoading for (int i = 0; i < symbolsA.Length; i++) { - if (!HeuristicallyEquals(symbolsA[i], symbolsB[i], tokenMap)) + if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap)) return false; } return true; } - return HeuristicallyEquals(typeA.FullName, typeB.FullName, new Dictionary<string, string>()); + return HeuristicallyEqualsImpl(typeA.FullName, typeB.FullName, new Dictionary<string, string>()); } /// <summary>Map a generic type placeholder (like <c>!0</c>) to its actual type.</summary> @@ -99,7 +97,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// <returns>Returns the previously-mapped type if applicable, else the <paramref name="type"/>.</returns> private string MapPlaceholder(string placeholder, string type, IDictionary<string, string> map) { - if (map.TryGetValue(placeholder, out string result)) + if (map.TryGetValue(placeholder, out string? result)) return result; map[placeholder] = type; diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index cae38637..de16a780 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework /// <param name="modAssembly">The mod assembly.</param> public void TrackAssemblies(IModMetadata metadata, Assembly modAssembly) { - this.ModNamesByAssembly[modAssembly.FullName] = metadata; + this.ModNamesByAssembly[modAssembly.FullName!] = metadata; } /// <summary>Get metadata for all loaded mods.</summary> @@ -62,7 +60,7 @@ namespace StardewModdingAPI.Framework /// <summary>Get metadata for a loaded mod.</summary> /// <param name="uniqueID">The mod's unique ID.</param> /// <returns>Returns the matching mod's metadata, or <c>null</c> if not found.</returns> - public IModMetadata Get(string uniqueID) + public IModMetadata? Get(string uniqueID) { // normalize search ID if (string.IsNullOrWhiteSpace(uniqueID)) @@ -76,14 +74,14 @@ namespace StardewModdingAPI.Framework /// <summary>Get the mod metadata from one of its assemblies.</summary> /// <param name="type">The type to check.</param> /// <returns>Returns the mod name, or <c>null</c> if the type isn't part of a known mod.</returns> - public IModMetadata GetFrom(Type type) + public IModMetadata? GetFrom(Type? type) { // null if (type == null) return null; // known type - string assemblyName = type.Assembly.FullName; + string assemblyName = type.Assembly.FullName!; if (this.ModNamesByAssembly.ContainsKey(assemblyName)) return this.ModNamesByAssembly[assemblyName]; @@ -93,7 +91,7 @@ namespace StardewModdingAPI.Framework /// <summary>Get the friendly name for the closest assembly registered as a source of deprecation warnings.</summary> /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns> - public IModMetadata GetFromStack() + public IModMetadata? GetFromStack() { // get stack frames StackTrace stack = new(); @@ -102,8 +100,8 @@ namespace StardewModdingAPI.Framework // search stack for a source assembly foreach (StackFrame frame in frames) { - MethodBase method = frame.GetMethod(); - IModMetadata mod = this.GetFrom(method.ReflectedType); + MethodBase? method = frame.GetMethod(); + IModMetadata? mod = this.GetFrom(method?.ReflectedType); if (mod != null) return mod; } diff --git a/src/SMAPI/Framework/Reflection/ReflectedField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs index 921876b9..197a246e 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedField.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -15,8 +13,8 @@ namespace StardewModdingAPI.Framework.Reflection /// <summary>The type that has the field.</summary> private readonly Type ParentType; - /// <summary>The object that has the instance field (if applicable).</summary> - private readonly object Parent; + /// <summary>The object that has the instance field, or <c>null</c> for a static field.</summary> + private readonly object? Parent; /// <summary>The display name shown in error messages.</summary> private string DisplayName => $"{this.ParentType.FullName}::{this.FieldInfo.Name}"; @@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// <summary>Construct an instance.</summary> /// <param name="parentType">The type that has the field.</param> - /// <param name="obj">The object that has the instance field (if applicable).</param> + /// <param name="obj">The object that has the instance field, or <c>null</c> for a static field.</param> /// <param name="field">The reflection metadata.</param> /// <param name="isStatic">Whether the field is static.</param> /// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="field"/> is null.</exception> /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static field, or not null for a static field.</exception> - public ReflectedField(Type parentType, object obj, FieldInfo field, bool isStatic) + public ReflectedField(Type parentType, object? obj, FieldInfo field, bool isStatic) { // validate if (parentType == null) @@ -58,11 +56,11 @@ namespace StardewModdingAPI.Framework.Reflection } /// <inheritdoc /> - public TValue GetValue() + public TValue? GetValue() { try { - return (TValue)this.FieldInfo.GetValue(this.Parent); + return (TValue?)this.FieldInfo.GetValue(this.Parent); } catch (InvalidCastException) { @@ -75,7 +73,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// <inheritdoc /> - public void SetValue(TValue value) + public void SetValue(TValue? value) { try { diff --git a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs index 50f89b40..c245e843 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -14,8 +12,8 @@ namespace StardewModdingAPI.Framework.Reflection /// <summary>The type that has the method.</summary> private readonly Type ParentType; - /// <summary>The object that has the instance method (if applicable).</summary> - private readonly object Parent; + /// <summary>The object that has the instance method, or <c>null</c> for a static method.</summary> + private readonly object? Parent; /// <summary>The display name shown in error messages.</summary> private string DisplayName => $"{this.ParentType.FullName}::{this.MethodInfo.Name}"; @@ -33,12 +31,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// <summary>Construct an instance.</summary> /// <param name="parentType">The type that has the method.</param> - /// <param name="obj">The object that has the instance method(if applicable).</param> + /// <param name="obj">The object that has the instance method, or <c>null</c> for a static method.</param> /// <param name="method">The reflection metadata.</param> /// <param name="isStatic">Whether the method is static.</param> /// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="method"/> is null.</exception> /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static method, or not null for a static method.</exception> - public ReflectedMethod(Type parentType, object obj, MethodInfo method, bool isStatic) + public ReflectedMethod(Type parentType, object? obj, MethodInfo method, bool isStatic) { // validate if (parentType == null) @@ -57,10 +55,10 @@ namespace StardewModdingAPI.Framework.Reflection } /// <inheritdoc /> - public TValue Invoke<TValue>(params object[] arguments) + public TValue? Invoke<TValue>(params object?[] arguments) { // invoke method - object result; + object? result; try { result = this.MethodInfo.Invoke(this.Parent, arguments); @@ -77,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection // cast return value try { - return (TValue)result; + return (TValue?)result; } catch (InvalidCastException) { @@ -86,7 +84,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// <inheritdoc /> - public void Invoke(params object[] arguments) + public void Invoke(params object?[] arguments) { // invoke method try diff --git a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs index a6d8c75c..638953a3 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -16,10 +14,10 @@ namespace StardewModdingAPI.Framework.Reflection private readonly string DisplayName; /// <summary>The underlying property getter.</summary> - private readonly Func<TValue> GetMethod; + private readonly Func<TValue?>? GetMethod; /// <summary>The underlying property setter.</summary> - private readonly Action<TValue> SetMethod; + private readonly Action<TValue?>? SetMethod; /********* @@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// <summary>Construct an instance.</summary> /// <param name="parentType">The type that has the property.</param> - /// <param name="obj">The object that has the instance property (if applicable).</param> + /// <param name="obj">The object that has the instance property, or <c>null</c> for a static property.</param> /// <param name="property">The reflection metadata.</param> /// <param name="isStatic">Whether the property is static.</param> /// <exception cref="ArgumentNullException">The <paramref name="parentType"/> or <paramref name="property"/> is null.</exception> /// <exception cref="ArgumentException">The <paramref name="obj"/> is null for a non-static property, or not null for a static property.</exception> - public ReflectedProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) + public ReflectedProperty(Type parentType, object? obj, PropertyInfo property, bool isStatic) { // validate input if (parentType == null) @@ -58,13 +56,13 @@ namespace StardewModdingAPI.Framework.Reflection this.PropertyInfo = property; if (this.PropertyInfo.GetMethod != null) - this.GetMethod = (Func<TValue>)Delegate.CreateDelegate(typeof(Func<TValue>), obj, this.PropertyInfo.GetMethod); + this.GetMethod = (Func<TValue?>)Delegate.CreateDelegate(typeof(Func<TValue?>), obj, this.PropertyInfo.GetMethod); if (this.PropertyInfo.SetMethod != null) - this.SetMethod = (Action<TValue>)Delegate.CreateDelegate(typeof(Action<TValue>), obj, this.PropertyInfo.SetMethod); + this.SetMethod = (Action<TValue?>)Delegate.CreateDelegate(typeof(Action<TValue?>), obj, this.PropertyInfo.SetMethod); } /// <inheritdoc /> - public TValue GetValue() + public TValue? GetValue() { if (this.GetMethod == null) throw new InvalidOperationException($"The {this.DisplayName} property has no get method."); @@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// <inheritdoc /> - public void SetValue(TValue value) + public void SetValue(TValue? value) { if (this.SetMethod == null) throw new InvalidOperationException($"The {this.DisplayName} property has no set method."); diff --git a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs index 8718bcb1..37996b0f 100644 --- a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Rendering /// <param name="tile">The tile to draw.</param> /// <param name="location">The tile position to draw.</param> /// <param name="layerDepth">The layer depth at which to draw.</param> - public override void DrawTile(Tile tile, Location location, float layerDepth) + public override void DrawTile(Tile? tile, Location location, float layerDepth) { // identical to XnaDisplayDevice if (tile == null) @@ -58,7 +56,7 @@ namespace StardewModdingAPI.Framework.Rendering /// <param name="tile">The tile being drawn.</param> private SpriteEffects GetSpriteEffects(Tile tile) { - return tile.Properties.TryGetValue("@Flip", out PropertyValue propertyValue) && int.TryParse(propertyValue, out int value) + return tile.Properties.TryGetValue("@Flip", out PropertyValue? propertyValue) && int.TryParse(propertyValue, out int value) ? (SpriteEffects)value : SpriteEffects.None; } @@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.Rendering /// <param name="tile">The tile being drawn.</param> private float GetRotation(Tile tile) { - if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue propertyValue) || !int.TryParse(propertyValue, out int value)) + if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue? propertyValue) || !int.TryParse(propertyValue, out int value)) return 0; value %= 360; diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs index 21edaedd..94b13378 100644 --- a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -91,7 +89,7 @@ namespace StardewModdingAPI.Framework.Rendering /// <param name="tile">The tile to draw.</param> /// <param name="location">The tile position to draw.</param> /// <param name="layerDepth">The layer depth at which to draw.</param> - public virtual void DrawTile(Tile tile, Location location, float layerDepth) + public virtual void DrawTile(Tile? tile, Location location, float layerDepth) { if (tile == null) return; diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 7ca89eec..4fa7fe7b 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -48,10 +46,10 @@ namespace StardewModdingAPI.Framework private readonly Action<string> ExitGameImmediately; /// <summary>The initial override for <see cref="Input"/>. This value is null after initialization.</summary> - private SInputState InitialInput; + private SInputState? InitialInput; /// <summary>The initial override for <see cref="Multiplayer"/>. This value is null after initialization.</summary> - private SMultiplayer InitialMultiplayer; + private SMultiplayer? InitialMultiplayer; /// <summary>Raised when the instance is updating its state (roughly 60 times per second).</summary> private readonly Action<SGame, GameTime, Action> OnUpdating; @@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework public Task NewDayTask => Game1._newDayTask; /// <summary>Monitors the entire game state for changes.</summary> - public WatcherCore Watchers { get; private set; } + public WatcherCore Watchers { get; private set; } = null!; // initialized on first update tick /// <summary>A snapshot of the current <see cref="Watchers"/> state.</summary> public WatcherSnapshot WatcherSnapshot { get; } = new(); @@ -94,7 +92,7 @@ namespace StardewModdingAPI.Framework /// <summary>Construct a content manager to read game content files.</summary> /// <remarks>This must be static because the game accesses it before the <see cref="SGame"/> constructor is called.</remarks> [NonInstancedStatic] - public static Func<IServiceProvider, string, LocalizedContentManager> CreateContentManagerImpl; + public static Func<IServiceProvider, string, LocalizedContentManager>? CreateContentManagerImpl; /********* @@ -138,11 +136,10 @@ namespace StardewModdingAPI.Framework /// <remarks>This is intended for use by <see cref="Keybind"/> and shouldn't be used directly in most cases.</remarks> internal static SButtonState GetInputState(SButton button) { - SInputState input = Game1.input as SInputState; - if (input == null) + if (Game1.input is not SInputState inputHandler) throw new InvalidOperationException("SMAPI's input state is not in a ready state yet."); - return input.GetState(button); + return inputHandler.GetState(button); } /// <inheritdoc /> @@ -172,13 +169,11 @@ namespace StardewModdingAPI.Framework { base.Initialize(); - // The game resets public static fields after the class is constructed (see - // GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here. + // The game resets public static fields after the class is constructed (see GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here. Game1.input = this.InitialInput; Game1.multiplayer = this.InitialMultiplayer; - // The Initial* fields should no longer be used after this point, since mods may - // further override them after initialization. + // The Initial* fields should no longer be used after this point, since mods may further override them after initialization. this.InitialInput = null; this.InitialMultiplayer = null; } @@ -251,6 +246,7 @@ namespace StardewModdingAPI.Framework Context.IsInDrawLoop = false; } +#nullable disable /// <summary>Replicate the game's draw logic with some changes for SMAPI.</summary> /// <param name="gameTime">A snapshot of the game timing state.</param> /// <param name="target_screen">The render target, if any.</param> @@ -258,6 +254,7 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantBaseQualifier", Justification = "copied from game code as-is")] @@ -265,8 +262,9 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "RedundantExplicitNullableCreation", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "copied from game code as-is")] - [SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")] + [SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")] private void DrawImpl(GameTime gameTime, RenderTarget2D target_screen) { var events = this.Events; @@ -952,5 +950,6 @@ namespace StardewModdingAPI.Framework this.drawOverlays(Game1.spriteBatch); Game1.PopUIMode(); } +#nullable enable } } diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs index 7941e102..a7736c8b 100644 --- a/src/SMAPI/Framework/SModHooks.cs +++ b/src/SMAPI/Framework/SModHooks.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Threading.Tasks; using StardewValley; @@ -35,7 +33,7 @@ namespace StardewModdingAPI.Framework /// <param name="action">The vanilla <see cref="Game1.newDayAfterFade"/> logic.</param> public override void OnGame1_NewDayAfterFade(Action action) { - this.BeforeNewDayAfterFade?.Invoke(); + this.BeforeNewDayAfterFade(); action(); } diff --git a/src/SMAPI/Framework/SnapshotDiff.cs b/src/SMAPI/Framework/SnapshotDiff.cs index eb2aebe1..d659d2b4 100644 --- a/src/SMAPI/Framework/SnapshotDiff.cs +++ b/src/SMAPI/Framework/SnapshotDiff.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewModdingAPI.Framework.StateTracking; namespace StardewModdingAPI.Framework @@ -15,10 +13,10 @@ namespace StardewModdingAPI.Framework public bool IsChanged { get; private set; } /// <summary>The previous value.</summary> - public T Old { get; private set; } + public T? Old { get; private set; } /// <summary>The current value.</summary> - public T New { get; private set; } + public T? New { get; private set; } /********* diff --git a/src/SMAPI/Framework/SnapshotItemListDiff.cs b/src/SMAPI/Framework/SnapshotItemListDiff.cs index 97942783..76060db2 100644 --- a/src/SMAPI/Framework/SnapshotItemListDiff.cs +++ b/src/SMAPI/Framework/SnapshotItemListDiff.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Events; using StardewValley; @@ -48,7 +47,7 @@ namespace StardewModdingAPI.Framework /// <param name="stackSizes">The items with their previous stack sizes.</param> /// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param> /// <returns>Returns whether anything changed.</returns> - public static bool TryGetChanges(ISet<Item> added, ISet<Item> removed, IDictionary<Item, int> stackSizes, out SnapshotItemListDiff changes) + public static bool TryGetChanges(ISet<Item> added, ISet<Item> removed, IDictionary<Item, int> stackSizes, [NotNullWhen(true)] out SnapshotItemListDiff? changes) { KeyValuePair<Item, int>[] sizesChanged = stackSizes.Where(p => p.Key.Stack != p.Value).ToArray(); if (sizesChanged.Any() || added.Any() || removed.Any()) diff --git a/src/SMAPI/Framework/SnapshotListDiff.cs b/src/SMAPI/Framework/SnapshotListDiff.cs index 1d585c15..90066af1 100644 --- a/src/SMAPI/Framework/SnapshotListDiff.cs +++ b/src/SMAPI/Framework/SnapshotListDiff.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.StateTracking; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework /// <param name="isChanged">Whether the value changed since the last update.</param> /// <param name="removed">The removed values.</param> /// <param name="added">The added values.</param> - public void Update(bool isChanged, IEnumerable<T> removed, IEnumerable<T> added) + public void Update(bool isChanged, IEnumerable<T>? removed, IEnumerable<T>? added) { this.IsChanged = isChanged; diff --git a/src/SMAPI/Framework/StateTracking/ChestTracker.cs b/src/SMAPI/Framework/StateTracking/ChestTracker.cs index 28335200..c33a7498 100644 --- a/src/SMAPI/Framework/StateTracking/ChestTracker.cs +++ b/src/SMAPI/Framework/StateTracking/ChestTracker.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; @@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>Get the inventory changes since the last update, if anything changed.</summary> /// <param name="changes">The inventory changes, or <c>null</c> if nothing changed.</param> /// <returns>Returns whether anything changed.</returns> - public bool TryGetInventoryChanges(out SnapshotItemListDiff changes) + public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes) { return SnapshotItemListDiff.TryGetChanges(added: this.Added, removed: this.Removed, stackSizes: this.StackSizes, out changes); } diff --git a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs index 987e1820..9d8559b4 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// <returns>true if the specified objects are equal; otherwise, false.</returns> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { if (x == null) return y == null; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs index f6b04583..41b17e10 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// <returns>true if the specified objects are equal; otherwise, false.</returns> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { if (x == null) return y == null; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs index 8d3a7eb9..e6ece854 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// <returns>true if the specified objects are equal; otherwise, false.</returns> /// <param name="x">The first object to compare.</param> /// <param name="y">The second object to compare.</param> - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { return object.ReferenceEquals(x, y); } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs index 0d7f2ad2..f55e4cea 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Netcode; @@ -12,6 +10,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <typeparam name="TSerialDict">The serializable dictionary type that can store the keys and values.</typeparam> /// <typeparam name="TSelf">The net field instance type.</typeparam> internal class NetDictionaryWatcher<TKey, TValue, TField, TSerialDict, TSelf> : BaseDisposableWatcher, IDictionaryWatcher<TKey, TValue> + where TKey : notnull where TField : class, INetObject<INetSerializable>, new() where TSerialDict : IDictionary<TKey, TValue>, new() where TSelf : NetDictionary<TKey, TValue, TField, TSerialDict, TSelf> diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index 82e5387e..97aedca8 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -81,7 +79,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>A callback invoked when an entry is added or removed from the collection.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event arguments.</param> - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { @@ -90,8 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers } else { - TValue[] added = e.NewItems?.Cast<TValue>().ToArray(); - TValue[] removed = e.OldItems?.Cast<TValue>().ToArray(); + TValue[]? added = e.NewItems?.Cast<TValue>().ToArray(); + TValue[]? removed = e.OldItems?.Cast<TValue>().ToArray(); if (removed != null) { diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index 0b99914c..c4a4d0b9 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -20,7 +18,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>Get a watcher which compares values using their <see cref="object.Equals(object)"/> method. This method should only be used when <see cref="ForEquatable{T}"/> won't work, since this doesn't validate whether they're comparable.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="getValue">Get the current value.</param> - public static IValueWatcher<T> ForGenericEquality<T>(Func<T> getValue) where T : struct + public static IValueWatcher<T> ForGenericEquality<T>(Func<T> getValue) + where T : struct { return new ComparableWatcher<T>(getValue, new GenericEqualsComparer<T>()); } @@ -28,7 +27,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>Get a watcher for an <see cref="IEquatable{T}"/> value.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="getValue">Get the current value.</param> - public static IValueWatcher<T> ForEquatable<T>(Func<T> getValue) where T : IEquatable<T> + public static IValueWatcher<T> ForEquatable<T>(Func<T> getValue) + where T : IEquatable<T> { return new ComparableWatcher<T>(getValue, new EquatableComparer<T>()); } @@ -79,7 +79,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>Get a watcher for a net collection.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="collection">The net collection.</param> - public static ICollectionWatcher<T> ForNetCollection<T>(NetCollection<T> collection) where T : class, INetObject<INetSerializable> + public static ICollectionWatcher<T> ForNetCollection<T>(NetCollection<T> collection) + where T : class, INetObject<INetSerializable> { return new NetCollectionWatcher<T>(collection); } @@ -87,7 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <summary>Get a watcher for a net list.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="collection">The net list.</param> - public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection) where T : class, INetObject<INetSerializable> + public static ICollectionWatcher<T> ForNetList<T>(NetList<T, NetRef<T>> collection) + where T : class, INetObject<INetSerializable> { return new NetListWatcher<T>(collection); } @@ -100,6 +102,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// <typeparam name="TSelf">The net field instance type.</typeparam> /// <param name="field">The net field.</param> public static NetDictionaryWatcher<TKey, TValue, TField, TSerialDict, TSelf> ForNetDictionary<TKey, TValue, TField, TSerialDict, TSelf>(NetDictionary<TKey, TValue, TField, TSerialDict, TSelf> field) + where TKey : notnull where TField : class, INetObject<INetSerializable>, new() where TSerialDict : IDictionary<TKey, TValue>, new() where TSelf : NetDictionary<TKey, TValue, TField, TSerialDict, TSelf> diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 9c2ff7f0..ff72a19b 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -132,20 +130,20 @@ namespace StardewModdingAPI.Framework.StateTracking private void UpdateChestWatcherList(IEnumerable<KeyValuePair<Vector2, SObject>> added, IEnumerable<KeyValuePair<Vector2, SObject>> removed) { // remove unused watchers - foreach (KeyValuePair<Vector2, SObject> pair in removed) + foreach ((Vector2 tile, SObject? obj) in removed) { - if (pair.Value is Chest && this.ChestWatchers.TryGetValue(pair.Key, out ChestTracker watcher)) + if (obj is Chest && this.ChestWatchers.TryGetValue(tile, out ChestTracker? watcher)) { watcher.Dispose(); - this.ChestWatchers.Remove(pair.Key); + this.ChestWatchers.Remove(tile); } } // add new watchers - foreach (KeyValuePair<Vector2, SObject> pair in added) + foreach ((Vector2 tile, SObject? obj) in added) { - if (pair.Value is Chest chest && !this.ChestWatchers.ContainsKey(pair.Key)) - this.ChestWatchers.Add(pair.Key, new ChestTracker(chest)); + if (obj is Chest chest && !this.ChestWatchers.ContainsKey(tile)) + this.ChestWatchers.Add(tile, new ChestTracker(chest)); } } } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 3d13f92b..0d0469d7 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Microsoft.Xna.Framework; using StardewValley; @@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots this.ChestItems.Clear(); foreach (ChestTracker tracker in watcher.ChestWatchers.Values) { - if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff changes)) + if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff? changes)) this.ChestItems[tracker.Chest] = changes; } } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs index 1d43ef26..27a891de 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.Xna.Framework; using StardewValley; using StardewValley.Menus; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots public SnapshotDiff<Point> WindowSize { get; } = new(); /// <summary>Tracks changes to the current player.</summary> - public PlayerSnapshot CurrentPlayer { get; private set; } + public PlayerSnapshot? CurrentPlayer { get; private set; } /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> public SnapshotDiff<int> Time { get; } = new(); @@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots // update snapshots this.WindowSize.Update(watchers.WindowSizeWatcher); this.Locale.Update(watchers.LocaleWatcher); - this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker); + this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker!); this.Time.Update(watchers.TimeWatcher); this.SaveID.Update(watchers.SaveIdWatcher); this.Locations.Update(watchers.LocationsWatcher); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs index 88aac0df..59f94942 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; @@ -44,7 +42,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots // update locations foreach (LocationTracker locationWatcher in watcher.Locations) { - if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot snapshot)) + if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot? snapshot)) this.LocationsDict[locationWatcher.Location] = snapshot = new LocationSnapshot(locationWatcher.Location); snapshot.Update(locationWatcher); diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index ab02d7d5..817a6011 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -27,10 +25,10 @@ namespace StardewModdingAPI.Framework.StateTracking private readonly ICollectionWatcher<GameLocation> VolcanoLocationListWatcher; /// <summary>A lookup of the tracked locations.</summary> - private IDictionary<GameLocation, LocationTracker> LocationDict { get; } = new Dictionary<GameLocation, LocationTracker>(new ObjectReferenceComparer<GameLocation>()); + private Dictionary<GameLocation, LocationTracker> LocationDict { get; } = new(new ObjectReferenceComparer<GameLocation>()); /// <summary>A lookup of registered buildings and their indoor location.</summary> - private readonly IDictionary<Building, GameLocation> BuildingIndoors = new Dictionary<Building, GameLocation>(new ObjectReferenceComparer<Building>()); + private readonly Dictionary<Building, GameLocation?> BuildingIndoors = new(new ObjectReferenceComparer<Building>()); /********* @@ -101,10 +99,9 @@ namespace StardewModdingAPI.Framework.StateTracking } // detect building interiors changed (e.g. construction completed) - foreach (KeyValuePair<Building, GameLocation> pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value))) + foreach ((Building building, GameLocation? oldIndoors) in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value))) { - GameLocation oldIndoors = pair.Value; - GameLocation newIndoors = pair.Key.indoors.Value; + GameLocation? newIndoors = building.indoors.Value; if (oldIndoors != null) this.Added.Add(oldIndoors); @@ -189,19 +186,19 @@ namespace StardewModdingAPI.Framework.StateTracking ****/ /// <summary>Add the given building.</summary> /// <param name="building">The building to add.</param> - public void Add(Building building) + public void Add(Building? building) { if (building == null) return; - GameLocation indoors = building.indoors.Value; + GameLocation? indoors = building.indoors.Value; this.BuildingIndoors[building] = indoors; this.Add(indoors); } /// <summary>Add the given location.</summary> /// <param name="location">The location to add.</param> - public void Add(GameLocation location) + public void Add(GameLocation? location) { if (location == null) return; @@ -220,7 +217,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>Remove the given building.</summary> /// <param name="building">The building to remove.</param> - public void Remove(Building building) + public void Remove(Building? building) { if (building == null) return; @@ -231,12 +228,12 @@ namespace StardewModdingAPI.Framework.StateTracking /// <summary>Remove the given location.</summary> /// <param name="location">The location to remove.</param> - public void Remove(GameLocation location) + public void Remove(GameLocation? location) { if (location == null) return; - if (this.LocationDict.TryGetValue(location, out LocationTracker watcher)) + if (this.LocationDict.TryGetValue(location, out LocationTracker? watcher)) { // track change this.Removed.Add(location); diff --git a/src/SMAPI/Framework/Translator.cs b/src/SMAPI/Framework/Translator.cs index 144b043c..794e3cde 100644 --- a/src/SMAPI/Framework/Translator.cs +++ b/src/SMAPI/Framework/Translator.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; @@ -23,7 +22,7 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ - /// <summary>The current locale.</summary> + /// <summary>The current locale code like <c>fr-FR</c>, or an empty string for English.</summary> public string Locale { get; private set; } /// <summary>The game's current language code.</summary> @@ -39,9 +38,10 @@ namespace StardewModdingAPI.Framework this.SetLocale(string.Empty, LocalizedContentManager.LanguageCode.en); } - /// <summary>Set the current locale and precache translations.</summary> + /// <summary>Set the current locale and pre-cache translations.</summary> /// <param name="locale">The current locale.</param> /// <param name="localeEnum">The game's current language code.</param> + [MemberNotNull(nameof(Translator.ForLocale), nameof(Translator.Locale))] public void SetLocale(string locale, LocalizedContentManager.LanguageCode localeEnum) { this.Locale = locale.ToLower().Trim(); @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework this.ForLocale = new Dictionary<string, Translation>(StringComparer.OrdinalIgnoreCase); foreach (string key in this.GetAllKeysRaw()) { - string text = this.GetRaw(key, locale, withFallback: true); + string? text = this.GetRaw(key, locale, withFallback: true); this.ForLocale.Add(key, new Translation(this.Locale, key, text)); } } @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework /// <param name="key">The translation key.</param> public Translation Get(string key) { - this.ForLocale.TryGetValue(key, out Translation translation); + this.ForLocale.TryGetValue(key, out Translation? translation); return translation ?? new Translation(this.Locale, key, null); } @@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework foreach (var localeSet in this.All) { string locale = localeSet.Key; - string text = this.GetRaw(key, locale, withFallback); + string? text = this.GetRaw(key, locale, withFallback); if (text != null) translations[locale] = new Translation(locale, key, text); @@ -128,13 +128,13 @@ namespace StardewModdingAPI.Framework /// <param name="key">The translation key.</param> /// <param name="locale">The locale to get.</param> /// <param name="withFallback">Whether to add duplicate translations for locale fallback. For example, if a translation is defined in <c>default.json</c> but not <c>fr.json</c>, setting this to true will add a <c>fr</c> entry which duplicates the default text.</param> - private string GetRaw(string key, string locale, bool withFallback) + private string? GetRaw(string key, string locale, bool withFallback) { foreach (string next in this.GetRelevantLocales(locale)) { - string translation = null; + string? translation = null; bool hasTranslation = - this.All.TryGetValue(next, out IDictionary<string, string> translations) + this.All.TryGetValue(next, out IDictionary<string, string>? translations) && translations.TryGetValue(key, out translation); if (hasTranslation) diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs index 94ce0069..20d206e2 100644 --- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs +++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -9,6 +7,7 @@ namespace StardewModdingAPI.Framework.Utilities /// <typeparam name="TKey">The dictionary key type.</typeparam> /// <typeparam name="TValue">The dictionary value type.</typeparam> internal class TickCacheDictionary<TKey, TValue> + where TKey : notnull { /********* ** Fields @@ -36,7 +35,7 @@ namespace StardewModdingAPI.Framework.Utilities } // fetch value - if (!this.Cache.TryGetValue(cacheKey, out TValue cached)) + if (!this.Cache.TryGetValue(cacheKey, out TValue? cached)) this.Cache[cacheKey] = cached = get(); return cached; } diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index bd8d3367..5e20ac7b 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Xna.Framework; @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework public readonly IValueWatcher<Point> WindowSizeWatcher; /// <summary>Tracks changes to the current player.</summary> - public PlayerTracker CurrentPlayerTracker; + public PlayerTracker? CurrentPlayerTracker; /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> public readonly IValueWatcher<int> TimeWatcher; |