enable nullable annotations for manifests (#837)
This commit is contained in:
parent
ab6cf45b03
commit
e58e8a2283
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -52,11 +50,11 @@ namespace SMAPI.Tests.Core
|
|||
|
||||
// act
|
||||
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
|
||||
IModMetadata mod = mods.FirstOrDefault();
|
||||
IModMetadata? mod = mods.FirstOrDefault();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, mods.Length, 0, $"Expected to find one manifest, found {mods.Length} instead.");
|
||||
Assert.AreEqual(ModMetadataStatus.Failed, mod.Status, "The mod metadata was not marked failed.");
|
||||
Assert.AreEqual(ModMetadataStatus.Failed, mod!.Status, "The mod metadata was not marked failed.");
|
||||
Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set.");
|
||||
}
|
||||
|
||||
|
@ -91,12 +89,12 @@ namespace SMAPI.Tests.Core
|
|||
|
||||
// act
|
||||
IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray();
|
||||
IModMetadata mod = mods.FirstOrDefault();
|
||||
IModMetadata? mod = mods.FirstOrDefault();
|
||||
|
||||
// assert
|
||||
Assert.AreEqual(1, mods.Length, 0, "Expected to find one manifest.");
|
||||
Assert.IsNotNull(mod, "The loaded manifest shouldn't be null.");
|
||||
Assert.AreEqual(null, mod.DataRecord, "The data record should be null since we didn't provide one.");
|
||||
Assert.AreEqual(null, mod!.DataRecord, "The data record should be null since we didn't provide one.");
|
||||
Assert.AreEqual(modFolder, mod.DirectoryPath, "The directory path doesn't match.");
|
||||
Assert.AreEqual(null, mod.Error, "The error should be null since parsing should have succeeded.");
|
||||
Assert.AreEqual(ModMetadataStatus.Found, mod.Status, "The status doesn't match.");
|
||||
|
@ -215,7 +213,7 @@ namespace SMAPI.Tests.Core
|
|||
// create DLL
|
||||
string modFolder = Path.Combine(this.GetTempFolderPath(), Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(modFolder);
|
||||
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), "");
|
||||
File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll!), "");
|
||||
|
||||
// arrange
|
||||
Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict);
|
||||
|
@ -480,21 +478,20 @@ namespace SMAPI.Tests.Core
|
|||
/// <param name="contentPackForID">The <see cref="IManifest.ContentPackFor"/> value.</param>
|
||||
/// <param name="minimumApiVersion">The <see cref="IManifest.MinimumApiVersion"/> value.</param>
|
||||
/// <param name="dependencies">The <see cref="IManifest.Dependencies"/> value.</param>
|
||||
private Manifest GetManifest(string id = null, string name = null, string version = null, string entryDll = null, string contentPackForID = null, string minimumApiVersion = null, IManifestDependency[] dependencies = null)
|
||||
private Manifest GetManifest(string? id = null, string? name = null, string? version = null, string? entryDll = null, string? contentPackForID = null, string? minimumApiVersion = null, IManifestDependency[]? dependencies = null)
|
||||
{
|
||||
return new Manifest
|
||||
{
|
||||
UniqueID = id ?? $"{Sample.String()}.{Sample.String()}",
|
||||
Name = name ?? id ?? Sample.String(),
|
||||
Author = Sample.String(),
|
||||
Description = Sample.String(),
|
||||
Version = version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()),
|
||||
EntryDll = entryDll ?? $"{Sample.String()}.dll",
|
||||
ContentPackFor = contentPackForID != null ? new ManifestContentPackFor { UniqueID = contentPackForID } : null,
|
||||
MinimumApiVersion = minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null,
|
||||
Dependencies = dependencies ?? Array.Empty<IManifestDependency>(),
|
||||
UpdateKeys = Array.Empty<string>()
|
||||
};
|
||||
return new Manifest(
|
||||
uniqueId: id ?? $"{Sample.String()}.{Sample.String()}",
|
||||
name: name ?? id ?? Sample.String(),
|
||||
author: Sample.String(),
|
||||
description: Sample.String(),
|
||||
version: version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()),
|
||||
entryDll: entryDll ?? $"{Sample.String()}.dll",
|
||||
contentPackFor: contentPackForID != null ? new ManifestContentPackFor(contentPackForID, null) : null,
|
||||
minimumApiVersion: minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null,
|
||||
dependencies: dependencies ?? Array.Empty<IManifestDependency>(),
|
||||
updateKeys: Array.Empty<string>()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Get a randomized basic manifest.</summary>
|
||||
|
@ -510,7 +507,7 @@ namespace SMAPI.Tests.Core
|
|||
/// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param>
|
||||
private Mock<IModMetadata> GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false)
|
||||
{
|
||||
IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null)).ToArray());
|
||||
IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray());
|
||||
return this.GetMetadata(manifest, allowStatusChange);
|
||||
}
|
||||
|
||||
|
@ -538,7 +535,7 @@ namespace SMAPI.Tests.Core
|
|||
/// <summary>Set up a mock mod metadata for <see cref="ModResolver.ValidateManifests"/>.</summary>
|
||||
/// <param name="mod">The mock mod metadata.</param>
|
||||
/// <param name="modRecord">The extra metadata about the mod from SMAPI's internal data (if any).</param>
|
||||
private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields modRecord = null)
|
||||
private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields? modRecord = null)
|
||||
{
|
||||
mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found);
|
||||
mod.Setup(p => p.DataRecord).Returns(() => null);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StardewModdingAPI
|
||||
|
@ -23,16 +21,16 @@ namespace StardewModdingAPI
|
|||
ISemanticVersion Version { get; }
|
||||
|
||||
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
|
||||
ISemanticVersion MinimumApiVersion { get; }
|
||||
ISemanticVersion? MinimumApiVersion { get; }
|
||||
|
||||
/// <summary>The unique mod ID.</summary>
|
||||
string UniqueID { get; }
|
||||
|
||||
/// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary>
|
||||
string EntryDll { get; }
|
||||
string? EntryDll { get; }
|
||||
|
||||
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="EntryDll"/>.</summary>
|
||||
IManifestContentPackFor ContentPackFor { get; }
|
||||
IManifestContentPackFor? ContentPackFor { get; }
|
||||
|
||||
/// <summary>The other mods that must be loaded before this mod.</summary>
|
||||
IManifestDependency[] Dependencies { get; }
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary>
|
||||
|
@ -9,6 +7,6 @@ namespace StardewModdingAPI
|
|||
string UniqueID { get; }
|
||||
|
||||
/// <summary>The minimum required version (if any).</summary>
|
||||
ISemanticVersion MinimumVersion { get; }
|
||||
ISemanticVersion? MinimumVersion { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#nullable disable
|
||||
|
||||
namespace StardewModdingAPI
|
||||
{
|
||||
/// <summary>A mod dependency listed in a mod manifest.</summary>
|
||||
|
@ -12,7 +10,7 @@ namespace StardewModdingAPI
|
|||
string UniqueID { get; }
|
||||
|
||||
/// <summary>The minimum required version (if any).</summary>
|
||||
ISemanticVersion MinimumVersion { get; }
|
||||
ISemanticVersion? MinimumVersion { get; }
|
||||
|
||||
/// <summary>Whether the dependency must be installed to use the mod.</summary>
|
||||
bool IsRequired { get; }
|
||||
|
|
|
@ -171,14 +171,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
|||
}
|
||||
}
|
||||
|
||||
// normalize display fields
|
||||
if (manifest != null)
|
||||
{
|
||||
manifest.Name = this.StripNewlines(manifest.Name);
|
||||
manifest.Description = this.StripNewlines(manifest.Description);
|
||||
manifest.Author = this.StripNewlines(manifest.Author);
|
||||
}
|
||||
|
||||
// get mod type
|
||||
ModType type;
|
||||
{
|
||||
|
@ -365,12 +357,5 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
|
|||
|
||||
return hasVortexMarker;
|
||||
}
|
||||
|
||||
/// <summary>Strip newlines from a string.</summary>
|
||||
/// <param name="input">The input to strip.</param>
|
||||
private string StripNewlines(string input)
|
||||
{
|
||||
return input?.Replace("\r", "").Replace("\n", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using StardewModdingAPI.Toolkit.Serialization.Converters;
|
||||
|
||||
|
@ -15,48 +13,45 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod name.</summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>A brief description of the mod.</summary>
|
||||
public string Description { get; set; }
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>The mod author's name.</summary>
|
||||
public string Author { get; set; }
|
||||
public string Author { get; }
|
||||
|
||||
/// <summary>The mod version.</summary>
|
||||
public ISemanticVersion Version { get; set; }
|
||||
public ISemanticVersion Version { get; }
|
||||
|
||||
/// <summary>The minimum SMAPI version required by this mod, if any.</summary>
|
||||
public ISemanticVersion MinimumApiVersion { get; set; }
|
||||
public ISemanticVersion? MinimumApiVersion { get; }
|
||||
|
||||
/// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary>
|
||||
public string EntryDll { get; set; }
|
||||
public string? EntryDll { get; }
|
||||
|
||||
/// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary>
|
||||
[JsonConverter(typeof(ManifestContentPackForConverter))]
|
||||
public IManifestContentPackFor ContentPackFor { get; set; }
|
||||
public IManifestContentPackFor? ContentPackFor { get; }
|
||||
|
||||
/// <summary>The other mods that must be loaded before this mod.</summary>
|
||||
[JsonConverter(typeof(ManifestDependencyArrayConverter))]
|
||||
public IManifestDependency[] Dependencies { get; set; }
|
||||
public IManifestDependency[] Dependencies { get; }
|
||||
|
||||
/// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary>
|
||||
public string[] UpdateKeys { get; set; }
|
||||
public string[] UpdateKeys { get; private set; }
|
||||
|
||||
/// <summary>The unique mod ID.</summary>
|
||||
public string UniqueID { get; set; }
|
||||
public string UniqueID { get; }
|
||||
|
||||
/// <summary>Any manifest fields which didn't match a valid field.</summary>
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, object> ExtraFields { get; set; }
|
||||
public IDictionary<string, object> ExtraFields { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
public Manifest() { }
|
||||
|
||||
/// <summary>Construct an instance for a transitional content pack.</summary>
|
||||
/// <param name="uniqueID">The unique mod ID.</param>
|
||||
/// <param name="name">The mod name.</param>
|
||||
|
@ -64,24 +59,71 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
|||
/// <param name="description">A brief description of the mod.</param>
|
||||
/// <param name="version">The mod version.</param>
|
||||
/// <param name="contentPackFor">The modID which will read this as a content pack.</param>
|
||||
public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null)
|
||||
public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string? contentPackFor = null)
|
||||
: this(
|
||||
uniqueId: uniqueID,
|
||||
name: name,
|
||||
author: author,
|
||||
description: description,
|
||||
version: version,
|
||||
minimumApiVersion: null,
|
||||
entryDll: null,
|
||||
contentPackFor: contentPackFor != null
|
||||
? new ManifestContentPackFor(contentPackFor, null)
|
||||
: null,
|
||||
dependencies: null,
|
||||
updateKeys: null
|
||||
)
|
||||
{ }
|
||||
|
||||
/// <summary>Construct an instance for a transitional content pack.</summary>
|
||||
/// <param name="uniqueId">The unique mod ID.</param>
|
||||
/// <param name="name">The mod name.</param>
|
||||
/// <param name="author">The mod author's name.</param>
|
||||
/// <param name="description">A brief description of the mod.</param>
|
||||
/// <param name="version">The mod version.</param>
|
||||
/// <param name="minimumApiVersion">The minimum SMAPI version required by this mod, if any.</param>
|
||||
/// <param name="entryDll">The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</param>
|
||||
/// <param name="contentPackFor">The modID which will read this as a content pack.</param>
|
||||
/// <param name="dependencies">The other mods that must be loaded before this mod.</param>
|
||||
/// <param name="updateKeys">The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</param>
|
||||
[JsonConstructor]
|
||||
public Manifest(string uniqueId, string name, string author, string description, ISemanticVersion version, ISemanticVersion? minimumApiVersion, string? entryDll, IManifestContentPackFor? contentPackFor, IManifestDependency[]? dependencies, string[]? updateKeys)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Author = author;
|
||||
this.Description = description;
|
||||
this.UniqueID = this.NormalizeWhitespace(uniqueId);
|
||||
this.Name = this.NormalizeWhitespace(name);
|
||||
this.Author = this.NormalizeWhitespace(author);
|
||||
this.Description = this.NormalizeWhitespace(description);
|
||||
this.Version = version;
|
||||
this.UniqueID = uniqueID;
|
||||
this.UpdateKeys = Array.Empty<string>();
|
||||
this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor };
|
||||
this.MinimumApiVersion = minimumApiVersion;
|
||||
this.EntryDll = this.NormalizeWhitespace(entryDll);
|
||||
this.ContentPackFor = contentPackFor;
|
||||
this.Dependencies = dependencies ?? Array.Empty<IManifestDependency>();
|
||||
this.UpdateKeys = updateKeys ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>Normalize the model after it's deserialized.</summary>
|
||||
/// <param name="context">The deserialization context.</param>
|
||||
[OnDeserialized]
|
||||
public void OnDeserialized(StreamingContext context)
|
||||
/// <summary>Override the update keys loaded from the mod info.</summary>
|
||||
/// <param name="updateKeys">The new update keys to set.</param>
|
||||
internal void OverrideUpdateKeys(params string[] updateKeys)
|
||||
{
|
||||
this.Dependencies ??= Array.Empty<IManifestDependency>();
|
||||
this.UpdateKeys ??= Array.Empty<string>();
|
||||
this.UpdateKeys = updateKeys;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Normalize whitespace in a raw string.</summary>
|
||||
/// <param name="input">The input to strip.</param>
|
||||
#if NET5_0_OR_GREATER
|
||||
[return: NotNullIfNotNull("input")]
|
||||
#endif
|
||||
private string? NormalizeWhitespace(string? input)
|
||||
{
|
||||
return input
|
||||
?.Trim()
|
||||
.Replace("\r", "")
|
||||
.Replace("\n", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Serialization.Models
|
||||
{
|
||||
|
@ -9,9 +9,36 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The unique ID of the mod which can read this content pack.</summary>
|
||||
public string UniqueID { get; set; }
|
||||
public string UniqueID { get; }
|
||||
|
||||
/// <summary>The minimum required version (if any).</summary>
|
||||
public ISemanticVersion MinimumVersion { get; set; }
|
||||
public ISemanticVersion? MinimumVersion { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="uniqueId">The unique ID of the mod which can read this content pack.</param>
|
||||
/// <param name="minimumVersion">The minimum required version (if any).</param>
|
||||
public ManifestContentPackFor(string uniqueId, ISemanticVersion? minimumVersion)
|
||||
{
|
||||
this.UniqueID = this.NormalizeWhitespace(uniqueId);
|
||||
this.MinimumVersion = minimumVersion;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Normalize whitespace in a raw string.</summary>
|
||||
/// <param name="input">The input to strip.</param>
|
||||
#if NET5_0_OR_GREATER
|
||||
[return: NotNullIfNotNull("input")]
|
||||
#endif
|
||||
private string? NormalizeWhitespace(string? input)
|
||||
{
|
||||
return input?.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable disable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace StardewModdingAPI.Toolkit.Serialization.Models
|
||||
{
|
||||
|
@ -9,13 +10,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
|||
** Accessors
|
||||
*********/
|
||||
/// <summary>The unique mod ID to require.</summary>
|
||||
public string UniqueID { get; set; }
|
||||
public string UniqueID { get; }
|
||||
|
||||
/// <summary>The minimum required version (if any).</summary>
|
||||
public ISemanticVersion MinimumVersion { get; set; }
|
||||
public ISemanticVersion? MinimumVersion { get; }
|
||||
|
||||
/// <summary>Whether the dependency must be installed to use the mod.</summary>
|
||||
public bool IsRequired { get; set; }
|
||||
public bool IsRequired { get; }
|
||||
|
||||
|
||||
/*********
|
||||
|
@ -26,12 +27,39 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models
|
|||
/// <param name="minimumVersion">The minimum required version (if any).</param>
|
||||
/// <param name="required">Whether the dependency must be installed to use the mod.</param>
|
||||
public ManifestDependency(string uniqueID, string minimumVersion, bool required = true)
|
||||
{
|
||||
this.UniqueID = uniqueID;
|
||||
this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion)
|
||||
: this(
|
||||
uniqueID: uniqueID,
|
||||
minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion)
|
||||
? new SemanticVersion(minimumVersion)
|
||||
: null;
|
||||
: null,
|
||||
required: required
|
||||
)
|
||||
{ }
|
||||
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="uniqueID">The unique mod ID to require.</param>
|
||||
/// <param name="minimumVersion">The minimum required version (if any).</param>
|
||||
/// <param name="required">Whether the dependency must be installed to use the mod.</param>
|
||||
[JsonConstructor]
|
||||
public ManifestDependency(string uniqueID, ISemanticVersion? minimumVersion, bool required = true)
|
||||
{
|
||||
this.UniqueID = this.NormalizeWhitespace(uniqueID);
|
||||
this.MinimumVersion = minimumVersion;
|
||||
this.IsRequired = required;
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Normalize whitespace in a raw string.</summary>
|
||||
/// <param name="input">The input to strip.</param>
|
||||
#if NET5_0_OR_GREATER
|
||||
[return: NotNullIfNotNull("input")]
|
||||
#endif
|
||||
private string? NormalizeWhitespace(string? input)
|
||||
{
|
||||
return input?.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace StardewModdingAPI.Framework.ModLoading
|
|||
|
||||
// apply defaults
|
||||
if (manifest != null && dataRecord?.UpdateKey is not null)
|
||||
manifest.UpdateKeys = new[] { dataRecord.UpdateKey };
|
||||
manifest.OverrideUpdateKeys(dataRecord.UpdateKey);
|
||||
|
||||
// build metadata
|
||||
bool shouldIgnore = folder.Type == ModType.Ignored;
|
||||
|
|
Loading…
Reference in New Issue