pi: add validator for DevPlugins, with basic warnings about callbacks and commands

This commit is contained in:
goaaats 2024-03-27 19:35:29 +01:00
parent ba24b574d1
commit 614ea211a0
2 changed files with 187 additions and 0 deletions

View file

@ -2458,6 +2458,12 @@ internal class PluginInstallerWindow : Window, IDisposable
}
}
if (plugin is LocalDevPlugin devPlugin)
{
this.DrawDevPluginValidationIssues(devPlugin);
ImGuiHelpers.ScaledDummy(5);
}
// Controls
this.DrawPluginControlButton(plugin, availablePluginUpdate);
this.DrawDevPluginButtons(plugin);
@ -2961,6 +2967,70 @@ internal class PluginInstallerWindow : Window, IDisposable
}
}
private void DrawDevPluginValidationIssues(LocalDevPlugin devPlugin)
{
if (!devPlugin.IsLoaded)
{
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.DalamudGrey, "You have to load this plugin to see validation issues.");
}
else
{
var problems = PluginValidator.CheckForProblems(devPlugin);
if (problems.Count == 0)
{
ImGui.PushFont(InterfaceManager.IconFont);
ImGui.Text(FontAwesomeIcon.Check.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGuiHelpers.SafeTextColoredWrapped(ImGuiColors.HealerGreen, "No validation issues found in this plugin!");
}
else
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange);
using (var tree = ImRaii.TreeNode($"Found {problems.Count} validation issue{(problems.Count > 1 ? "s" : string.Empty)} in this plugin!"))
{
if (tree.Success)
{
foreach (var problem in problems)
{
ImGui.PushStyleColor(ImGuiCol.Text, problem.Severity switch
{
PluginValidator.ValidationSeverity.Fatal => ImGuiColors.DalamudRed,
PluginValidator.ValidationSeverity.Warning => ImGuiColors.DalamudOrange,
PluginValidator.ValidationSeverity.Information => ImGuiColors.TankBlue,
_ => ImGuiColors.DalamudGrey,
});
ImGui.PushFont(InterfaceManager.IconFont);
switch (problem.Severity)
{
case PluginValidator.ValidationSeverity.Fatal:
ImGui.Text(FontAwesomeIcon.TimesCircle.ToIconString());
break;
case PluginValidator.ValidationSeverity.Warning:
ImGui.Text(FontAwesomeIcon.ExclamationTriangle.ToIconString());
break;
case PluginValidator.ValidationSeverity.Information:
ImGui.Text(FontAwesomeIcon.InfoCircle.ToIconString());
break;
default:
throw new ArgumentOutOfRangeException();
}
ImGui.PopFont();
ImGui.SameLine();
ImGuiHelpers.SafeTextWrapped(problem.GetLocalizedDescription());
ImGui.PopStyleColor();
}
}
}
ImGui.PopStyleColor();
}
}
}
private void DrawDevPluginButtons(LocalPlugin localPlugin)
{
ImGui.SameLine();

View file

@ -0,0 +1,117 @@
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
{
/// <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!.UiBuilder.HasConfigUi)
problems.Add(new NoConfigUiProblem());
if (!plugin.DalamudInterface.UiBuilder.HasMainUi)
problems.Add(new NoMainUiProblem());
var cmdManager = Service<CommandManager>.Get();
foreach (var cmd in cmdManager.Commands.Where(x => x.Value.LoaderAssemblyName == plugin.InternalName && x.Value.ShowInHelp))
{
if (string.IsNullOrEmpty(cmd.Value.HelpMessage))
problems.Add(new CommandWithoutHelpTextProblem(cmd.Key));
}
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 register a config UI callback. If you have a settings window or section, please consider registering UiBuilder.OpenConfigUi.";
}
/// <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 draws any kind of ImGui windows, 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.";
}
}