Dalamud/Dalamud/Plugin/Internal/PluginValidator.cs
2024-06-30 00:58:04 +02:00

211 lines
7.9 KiB
C#

using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Command;
using Dalamud.Plugin.Internal.Types;
namespace Dalamud.Plugin.Internal;
/// <summary>
/// Class responsible for validating a dev plugin.
/// </summary>
internal static class PluginValidator
{
private static readonly char[] LineSeparator = new[] { ' ', '\n', '\r' };
/// <summary>
/// Represents the severity of a validation problem.
/// </summary>
public enum ValidationSeverity
{
/// <summary>
/// The problem is informational.
/// </summary>
Information,
/// <summary>
/// The problem is a warning.
/// </summary>
Warning,
/// <summary>
/// The problem is fatal.
/// </summary>
Fatal,
}
/// <summary>
/// Represents a validation problem.
/// </summary>
public interface IValidationProblem
{
/// <summary>
/// Gets the severity of the validation.
/// </summary>
public ValidationSeverity Severity { get; }
/// <summary>
/// Compute the localized description of the problem.
/// </summary>
/// <returns>Localized string to be shown to the developer.</returns>
public string GetLocalizedDescription();
}
/// <summary>
/// Check for problems in a plugin.
/// </summary>
/// <param name="plugin">The plugin to validate.</param>
/// <returns>An list of problems.</returns>
/// <exception cref="InvalidOperationException">Thrown when the plugin is not loaded. A plugin must be loaded to validate it.</exception>
public static IReadOnlyList<IValidationProblem> CheckForProblems(LocalDevPlugin plugin)
{
var problems = new List<IValidationProblem>();
if (!plugin.IsLoaded)
throw new InvalidOperationException("Plugin must be loaded to validate.");
if (!plugin.DalamudInterface!.LocalUiBuilder.HasConfigUi)
problems.Add(new NoConfigUiProblem());
if (!plugin.DalamudInterface.LocalUiBuilder.HasMainUi)
problems.Add(new NoMainUiProblem());
var cmdManager = Service<CommandManager>.Get();
foreach (var cmd in cmdManager.GetHandlersByAssemblyName(plugin.InternalName).Where(c => c.Key.CommandInfo.ShowInHelp))
{
if (string.IsNullOrEmpty(cmd.Key.CommandInfo.HelpMessage))
problems.Add(new CommandWithoutHelpTextProblem(cmd.Value));
}
if (plugin.Manifest.Tags == null || plugin.Manifest.Tags.Count == 0)
problems.Add(new NoTagsProblem());
if (string.IsNullOrEmpty(plugin.Manifest.Description) || plugin.Manifest.Description.Split(LineSeparator, StringSplitOptions.RemoveEmptyEntries).Length <= 1)
problems.Add(new NoDescriptionProblem());
if (string.IsNullOrEmpty(plugin.Manifest.Punchline))
problems.Add(new NoPunchlineProblem());
if (string.IsNullOrEmpty(plugin.Manifest.Name))
problems.Add(new NoNameProblem());
if (string.IsNullOrEmpty(plugin.Manifest.Author))
problems.Add(new NoAuthorProblem());
if (plugin.IsOutdated)
problems.Add(new WrongApiLevelProblem());
return problems;
}
/// <summary>
/// Representing a problem where the plugin does not have a config UI callback.
/// </summary>
public class NoConfigUiProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Warning;
/// <inheritdoc/>
public string GetLocalizedDescription() => "The plugin does not register a config UI callback. If you have a settings window or section, please consider registering UiBuilder.OpenConfigUi to open it.";
}
/// <summary>
/// Representing a problem where the plugin does not have a main UI callback.
/// </summary>
public class NoMainUiProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Warning;
/// <inheritdoc/>
public string GetLocalizedDescription() => "The plugin does not register a main UI callback. If your plugin has a window that could be considered the main entrypoint to its features, please consider registering UiBuilder.OpenMainUi to open the plugin's main window.";
}
/// <summary>
/// Representing a problem where a command does not have a help text.
/// </summary>
/// <param name="commandName">Name of the command.</param>
public class CommandWithoutHelpTextProblem(string commandName) : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Fatal;
/// <inheritdoc/>
public string GetLocalizedDescription() => $"The plugin has a command ({commandName}) without a help message. Please consider adding a help message to the command when registering it.";
}
/// <summary>
/// Representing a problem where a plugin does not have any tags in its manifest.
/// </summary>
public class NoTagsProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Information;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin does not have any tags in its manifest. Please consider adding some to make it easier for users to find your plugin in the installer.";
}
/// <summary>
/// Representing a problem where a plugin does not have a description in its manifest.
/// </summary>
public class NoDescriptionProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Information;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin does not have a description in its manifest, or it is very terse. Please consider adding one to give users more information about your plugin.";
}
/// <summary>
/// Representing a problem where a plugin has no punchline in its manifest.
/// </summary>
public class NoPunchlineProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Information;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin does not have a punchline in its manifest. Please consider adding one to give users a quick overview of what your plugin does.";
}
/// <summary>
/// Representing a problem where a plugin has no name in its manifest.
/// </summary>
public class NoNameProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Fatal;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin does not have a name in its manifest.";
}
/// <summary>
/// Representing a problem where a plugin has no author in its manifest.
/// </summary>
public class NoAuthorProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Fatal;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin does not have an author in its manifest.";
}
/// <summary>
/// Representing a problem where a plugin has an outdated API level.
/// </summary>
public class WrongApiLevelProblem : IValidationProblem
{
/// <inheritdoc/>
public ValidationSeverity Severity => ValidationSeverity.Fatal;
/// <inheritdoc/>
public string GetLocalizedDescription() => "Your plugin specifies an outdated API level. " +
"Please update it by updating DalamudPackager or Dalamud.NET.Sdk.";
}
}