Added component validators

This commit is contained in:
Anders Ejlersen 2026-05-18 20:37:05 +02:00
parent 35f271e12b
commit d7145ad772
30 changed files with 448 additions and 200 deletions

3
Editor/Objects.meta Normal file
View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3cf6340ace4c49589cc0467648d03ee7
timeCreated: 1778923205

View file

@ -14,6 +14,12 @@ namespace Module.ProjectValidator.Editor
private readonly Dictionary<int, MappingEntry> _instanceToSeverityMapping = new(); private readonly Dictionary<int, MappingEntry> _instanceToSeverityMapping = new();
public void Add(GUID assetGuid, string scenePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) public void Add(GUID assetGuid, string scenePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message)
{
var type = ProjectValidatorUtility.GetAttributeShortName(attribute);
Add(assetGuid, scenePath, fieldPath, type, severity, message);
}
public void Add(GUID assetGuid, string scenePath, string fieldPath, string type, EValidatorSeverity severity, string message)
{ {
Entries.Add(new Entry Entries.Add(new Entry
{ {
@ -23,7 +29,7 @@ namespace Module.ProjectValidator.Editor
FieldPath = fieldPath, FieldPath = fieldPath,
ScenePathRichText = ProjectValidatorUtility.ApplyRichTextToScenePath(scenePath), ScenePathRichText = ProjectValidatorUtility.ApplyRichTextToScenePath(scenePath),
FieldPathRichText = ProjectValidatorUtility.ApplyRichTextToFieldPath(fieldPath), FieldPathRichText = ProjectValidatorUtility.ApplyRichTextToFieldPath(fieldPath),
Type = ProjectValidatorUtility.GetAttributeShortName(attribute), Type = type,
Severity = severity, Severity = severity,
SeverityStr = severity.ToString(), SeverityStr = severity.ToString(),
SeverityResult = message SeverityResult = message

209
Editor/Objects/TypeTree.cs Normal file
View file

@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class TypeTree
{
public readonly Dictionary<Type, Entry> Types = new();
private readonly HashSet<Type> _scannedTypes = new();
public Entry Add(Type type, ValidatorList validatorList)
{
if (Types.TryGetValue(type, out var e))
return e;
if (!_scannedTypes.Add(type))
return null;
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var entry = new Entry();
if (validatorList.TryGetComponentValidator(type, out var componentValidators))
entry.AddComponents(componentValidators);
for (var i = 0; i < fields.Length; i++)
{
var fi = fields[i];
if (!IsFieldSerializable(fi))
continue;
var attributes = fi.GetCustomAttributes();
foreach (var attribute in attributes)
{
if (validatorList.TryGetAttributeValidator(attribute.GetType(), out var validator))
entry.AddField(fi, attribute, validator);
}
var nextType = GetFieldElementType(fi);
if (nextType == null || !IsTypeSerializable(nextType))
continue;
e = Add(nextType, validatorList);
if (e != null)
entry.AddField(fi, e);
}
if (entry.IsEmpty())
return null;
Types.Add(type, entry);
return entry;
}
private static Type GetFieldElementType(FieldInfo fi)
{
var type = fi.FieldType;
if (type.IsPrimitive || type.IsInterface || type.IsAbstract)
{
type = null;
}
else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
{
type = null;
}
else if (type.IsArray)
{
type = type.GetElementType();
}
else if (typeof(IEnumerable<object>).IsAssignableFrom(type))
{
var args = type.GenericTypeArguments;
type = args.Length == 1 ? type.GenericTypeArguments[0] : null;
}
if (type != null && !IsTypeSerializable(type))
type = null;
return type;
}
private static bool IsFieldSerializable(FieldInfo fieldInfo)
{
if (fieldInfo.IsNotSerialized)
return false;
if (fieldInfo.IsPrivate && fieldInfo.GetCustomAttribute<SerializeField>() == null)
return false;
return IsTypeSerializable(fieldInfo.FieldType);
}
private static bool IsTypeSerializable(Type type)
{
if (type == null)
return false;
if (type.IsInterface)
return false;
if (type.IsAbstract)
return false;
if (type.IsPrimitive)
return true;
if (type.IsEnum)
return true;
if (typeof(UnityEngine.Object).IsAssignableFrom(type))
return true;
if (type.IsArray)
{
var eType = type.GetElementType();
if (eType == null || !IsTypeSerializable(eType))
return false;
}
if (typeof(IEnumerable<object>).IsAssignableFrom(type))
{
var args = type.GetGenericArguments();
if (args.Length != 1 || !IsTypeSerializable(args[0]))
return false;
}
return type.GetCustomAttribute<SerializableAttribute>() != null;
}
public sealed class Entry
{
public List<ValidatorComponent> Components;
public List<ValidatorField> Fields;
public List<FieldEntry> Entries;
public void AddComponents(List<object> components)
{
Components ??= new List<ValidatorComponent>();
for (var i = 0; i < components.Count; i++)
{
Components.Add(new ValidatorComponent(components[i]));
}
}
public void AddField(FieldInfo fieldInfo, Attribute attribute, object validator)
{
Fields ??= new List<ValidatorField>();
Fields.Add(new ValidatorField(fieldInfo, attribute, validator));
}
public void AddField(FieldInfo fieldInfo, Entry entry)
{
Entries ??= new List<FieldEntry>();
Entries.Add(new FieldEntry(fieldInfo, entry));
}
public bool IsEmpty()
{
return Components == null && Fields == null && Entries == null;
}
}
public sealed class ValidatorField
{
public readonly FieldInfo FieldInfo;
public readonly Attribute Attribute;
public readonly object Validator;
public readonly MethodInfo ValidatorMethod;
public ValidatorField(FieldInfo fieldInfo, Attribute attribute, object validator)
{
FieldInfo = fieldInfo;
Attribute = attribute;
Validator = validator;
ValidatorMethod = validator.GetType().GetMethod("Validate");
}
}
public sealed class ValidatorComponent
{
public readonly object Validator;
public readonly MethodInfo ValidatorMethod;
public ValidatorComponent(object validator)
{
Validator = validator;
ValidatorMethod = validator.GetType().GetMethod("Validate");
}
}
public sealed class FieldEntry
{
public readonly FieldInfo FieldInfo;
public readonly Entry Entry;
public FieldEntry(FieldInfo fieldInfo, Entry entry)
{
FieldInfo = fieldInfo;
Entry = entry;
}
}
}
}

View file

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ValidatorList
{
private readonly Dictionary<Type, object> _attributeValidators = new();
private readonly Dictionary<Type, List<object>> _componentValidators = new();
public void AddAttribute(Type type)
{
if (type.IsInterface || type.IsAbstract)
return;
var typeValidator = type.GetInterfaces().FirstOrDefault(typeInterface => typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(IAttributeValidator<>));
var attType = typeValidator?.GetGenericArguments()[0];
if (attType == null)
return;
try
{
var instance = FormatterServices.GetUninitializedObject(type);
_attributeValidators.Add(attType, instance);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
public void AddComponent(Type type)
{
if (type.IsInterface || type.IsAbstract)
return;
var typeValidator = type.GetInterfaces().FirstOrDefault(typeInterface => typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(IComponentValidator<>));
var componentType = typeValidator?.GetGenericArguments()[0];
if (componentType == null)
return;
try
{
var instance = FormatterServices.GetUninitializedObject(type);
if (_componentValidators.TryGetValue(componentType, out var list))
list.Add(instance);
else
_componentValidators.Add(componentType, new List<object> { instance });
}
catch (Exception e)
{
Debug.LogException(e);
}
}
public bool TryGetAttributeValidator(Type type, out object validatorInstance)
{
return _attributeValidators.TryGetValue(type, out validatorInstance);
}
public bool TryGetComponentValidator(Type type, out List<object> validatorInstances)
{
return _componentValidators.TryGetValue(type, out validatorInstances);
}
}
}

View file

@ -39,6 +39,7 @@ namespace Module.ProjectValidator.Editor
var container = new VisualElement { style = { flexDirection = FlexDirection.Column } }; var container = new VisualElement { style = { flexDirection = FlexDirection.Column } };
var propertyField = new PropertyField(serializedObject.FindProperty("assemblies"), "Assemblies"); var propertyField = new PropertyField(serializedObject.FindProperty("assemblies"), "Assemblies");
propertyField.RegisterCallback<ChangeEvent<string>>(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); propertyField.RegisterCallback<ChangeEvent<string>>(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true));
propertyField.RegisterValueChangeCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true));
container.Add(propertyField); container.Add(propertyField);
root.Add(container); root.Add(container);
root.Bind(serializedObject); root.Bind(serializedObject);

View file

@ -9,7 +9,7 @@ namespace Module.ProjectValidator.Editor
{ {
public static T LoadFirstAsset<T>(string name) where T : Object public static T LoadFirstAsset<T>(string name) where T : Object
{ {
var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{typeof(T).Name} {name}"); var guids = AssetDatabase.FindAssetGUIDs($"t:{typeof(T).Name} {name}");
return guids.Length != 0 ? AssetDatabase.LoadAssetByGUID<T>(guids[0]) : null; return guids.Length != 0 ? AssetDatabase.LoadAssetByGUID<T>(guids[0]) : null;
} }

View file

@ -41,11 +41,15 @@ namespace Module.ProjectValidator.Editor
internal static string GetAttributeShortName(Attribute attribute) internal static string GetAttributeShortName(Attribute attribute)
{ {
var str = attribute.GetType().Name; var str = attribute.GetType().Name;
var index = str.IndexOf("Attribute", StringComparison.Ordinal); str = str.Replace("Attribute", string.Empty);
str = ObjectNames.NicifyVariableName(str);
if (index != -1) return str;
str = str[..index]; }
internal static string GetComponentValidatorShortName(object obj)
{
var str = obj.GetType().Name;
str = str.Replace("ComponentValidator", string.Empty);
str = ObjectNames.NicifyVariableName(str); str = ObjectNames.NicifyVariableName(str);
return str; return str;
} }

View file

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using UnityEditor; using UnityEditor;
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
using UnityEngine; using UnityEngine;
using UnityEngine.Pool; using UnityEngine.Pool;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
namespace Module.ProjectValidator.Editor namespace Module.ProjectValidator.Editor
{ {
@ -19,6 +21,9 @@ namespace Module.ProjectValidator.Editor
{ {
if (!ProjectValidatorUtility.IsValidForRun()) if (!ProjectValidatorUtility.IsValidForRun())
return false; return false;
var stopwatch = new Stopwatch();
stopwatch.Start();
Initialize(); Initialize();
@ -34,6 +39,8 @@ namespace Module.ProjectValidator.Editor
if (showWindow) if (showWindow)
ProjectValidatorUtility.OpenWindow(); ProjectValidatorUtility.OpenWindow();
stopwatch.Stop();
Debug.Log(stopwatch.Elapsed.TotalMilliseconds + "ms");
return true; return true;
} }
@ -57,8 +64,9 @@ namespace Module.ProjectValidator.Editor
_validatorList = new ValidatorList(); _validatorList = new ValidatorList();
_typeTree = new TypeTree(); _typeTree = new TypeTree();
FetchAllValidators(); FetchAllAttributeValidators();
FetchAllTypesWithValidators<MonoBehaviour>(assemblies); FetchAllComponentValidators();
FetchAllTypesWithValidators<Component>(assemblies);
FetchAllTypesWithValidators<ScriptableObject>(assemblies); FetchAllTypesWithValidators<ScriptableObject>(assemblies);
_initialized = true; _initialized = true;
} }
@ -83,13 +91,23 @@ namespace Module.ProjectValidator.Editor
return assemblies.ToArray(); return assemblies.ToArray();
} }
private static void FetchAllValidators() private static void FetchAllAttributeValidators()
{ {
var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>)); var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>));
for (var i = 0; i < types.Count; i++) for (var i = 0; i < types.Count; i++)
{ {
_validatorList.Add(types[i]); _validatorList.AddAttribute(types[i]);
}
}
private static void FetchAllComponentValidators()
{
var types = TypeCache.GetTypesDerivedFrom(typeof(IComponentValidator<>));
for (var i = 0; i < types.Count; i++)
{
_validatorList.AddComponent(types[i]);
} }
} }
@ -101,7 +119,7 @@ namespace Module.ProjectValidator.Editor
{ {
var type = types[i]; var type = types[i];
if (Array.IndexOf(assemblies, type.Assembly) != -1) if (assemblies.Length == 0 || Array.IndexOf(assemblies, type.Assembly) != -1)
_typeTree.Add(type, _validatorList); _typeTree.Add(type, _validatorList);
} }
} }
@ -211,6 +229,22 @@ namespace Module.ProjectValidator.Editor
{ {
if (obj == null) if (obj == null)
return; return;
if (entry.Components != null)
{
for (var i = 0; i < entry.Components.Count; i++)
{
try
{
var component = entry.Components[i];
ValidateComponent(component, obj, assetGuid, scenePath, report);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
if (entry.Fields != null) if (entry.Fields != null)
{ {
@ -292,5 +326,20 @@ namespace Module.ProjectValidator.Editor
if (result.Severity != EValidatorSeverity.Valid) if (result.Severity != EValidatorSeverity.Valid)
report.Add(assetGuid, scenePath, fieldPath, field.Attribute, result.Severity, result.Message); report.Add(assetGuid, scenePath, fieldPath, field.Attribute, result.Severity, result.Message);
} }
private static void ValidateComponent(TypeTree.ValidatorComponent component, object value, GUID assetGuid, string scenePath, Report report)
{
using var _ = ListPool<ValidatorResult>.Get(out var results);
component.ValidatorMethod.Invoke(component.Validator, new[] { value, results });
var type = ProjectValidatorUtility.GetComponentValidatorShortName(component.Validator);
for (var i = 0; i < results.Count; i++)
{
var result = results[i];
if (result.Severity != EValidatorSeverity.Valid)
report.Add(assetGuid, scenePath, string.Empty, type, result.Severity, result.Message);
}
}
} }
} }

View file

@ -1,3 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 3cf6340ace4c49589cc0467648d03ee7 guid: 372e41dfe29c0ef468ae3554d6fbd9f8
timeCreated: 1778923205 folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b550bfeed6d6bb840972d41daa80bbd3 guid: e2f0308e61e26cd468af9c290586f6fb
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ComponentValidatorMeshCollider : IComponentValidator<MeshCollider>
{
public void Validate(MeshCollider component, List<ValidatorResult> results)
{
if (component.sharedMesh == null)
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh"));
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b42427573c2a4e68a9d3cc76773972c7
timeCreated: 1779125565

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ComponentValidatorMeshFilter : IComponentValidator<MeshFilter>
{
public void Validate(MeshFilter component, List<ValidatorResult> results)
{
if (component.sharedMesh == null)
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh"));
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e232459fff2b408aae26a2b1178e9b9f
timeCreated: 1779125497

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ComponentValidatorMeshRenderer : IComponentValidator<MeshRenderer>
{
public void Validate(MeshRenderer component, List<ValidatorResult> results)
{
var materials = component.sharedMaterials;
for (var i = 0; i < materials.Length; i++)
{
if (materials[i] == null)
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Missing material in slot #{i}"));
}
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: faf660447bfedc84f88e1e9809bc6583

View file

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Module.ProjectValidator.Editor
{
internal sealed class TypeTree
{
public readonly Dictionary<Type, Entry> Types = new();
public Entry Add(Type type, ValidatorList validatorList)
{
if (Types.TryGetValue(type, out var e))
return e;
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var entry = new Entry();
for (var i = 0; i < fields.Length; i++)
{
var fi = fields[i];
if (fi.IsNotSerialized)
continue;
var attributes = fi.GetCustomAttributes();
foreach (var attribute in attributes)
{
if (validatorList.TryGetValue(attribute.GetType(), out var validator))
entry.Add(fi, attribute, validator);
}
var nextType = fi.FieldType;
if (nextType.IsPrimitive || nextType.IsInterface || nextType.IsAbstract)
nextType = null;
else if (nextType.IsArray)
nextType = nextType.GetElementType();
else if (typeof(IEnumerable<object>).IsAssignableFrom(nextType))
nextType = nextType.GenericTypeArguments[0];
if (nextType == null || !nextType.IsSerializable)
continue;
e = Add(nextType, validatorList);
if (e != null)
entry.Add(fi, e);
}
if (entry.IsEmpty())
return null;
Types.Add(type, entry);
return entry;
}
public sealed class Entry
{
public List<ValidatorField> Fields;
public List<FieldEntry> Entries;
public void Add(FieldInfo fieldInfo, Attribute attribute, object validator)
{
Fields ??= new List<ValidatorField>();
Fields.Add(new ValidatorField(fieldInfo, attribute, validator));
}
public void Add(FieldInfo fieldInfo, Entry entry)
{
Entries ??= new List<FieldEntry>();
Entries.Add(new FieldEntry(fieldInfo, entry));
}
public bool IsEmpty()
{
return Fields == null && Entries == null;
}
}
public sealed class ValidatorField
{
public readonly FieldInfo FieldInfo;
public readonly Attribute Attribute;
public readonly object Validator;
public readonly MethodInfo ValidatorMethod;
public ValidatorField(FieldInfo fieldInfo, Attribute attribute, object validator)
{
FieldInfo = fieldInfo;
Attribute = attribute;
Validator = validator;
ValidatorMethod = validator.GetType().GetMethod("Validate");
}
}
public sealed class FieldEntry
{
public readonly FieldInfo FieldInfo;
public readonly Entry Entry;
public FieldEntry(FieldInfo fieldInfo, Entry entry)
{
FieldInfo = fieldInfo;
Entry = entry;
}
}
}
}

View file

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using UnityEngine;
namespace Module.ProjectValidator.Editor
{
internal sealed class ValidatorList
{
private readonly Dictionary<Type, object> _validators = new();
public void Add(Type type)
{
if (type.IsInterface || type.IsAbstract)
return;
var typeValidator = type.GetInterfaces().FirstOrDefault(typeInterface => typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(IAttributeValidator<>));
var attType = typeValidator?.GetGenericArguments()[0];
if (attType == null)
return;
try
{
var instance = FormatterServices.GetUninitializedObject(type);
_validators.Add(attType, instance);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
public bool TryGetValue(Type type, out object validatorInstance)
{
return _validators.TryGetValue(type, out validatorInstance);
}
}
}

View file

@ -18,6 +18,7 @@ namespace Module.ProjectValidator.Editor
{ {
var root = rootVisualElement; var root = rootVisualElement;
var asset = EditorAssetUtility.LoadFirstAsset<VisualTreeAsset>("UxmlEditorProjectValidatorWindow"); var asset = EditorAssetUtility.LoadFirstAsset<VisualTreeAsset>("UxmlEditorProjectValidatorWindow");
root.styleSheets.Add(EditorAssetUtility.LoadFirstAsset<StyleSheet>("StyleSheetEditorProjectValidatorWindow"));
root.Add(asset.Instantiate()); root.Add(asset.Instantiate());
root.Q<ToolbarButton>("button-run").clicked += OnToolbarButtonRunClicked; root.Q<ToolbarButton>("button-run").clicked += OnToolbarButtonRunClicked;

View file

@ -1,5 +1,4 @@
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True"> <ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<Style src="project://database/Assets/ProjectValidator/Editor/Window/Uxml/StyleSheetEditorProjectValidatorWindow.uss?fileID=7433441132597879392&amp;guid=360a611fbc24ba94895f4251f434f40b&amp;type=3#StyleSheetEditorProjectValidatorWindow"/>
<uie:Toolbar name="toolbar"> <uie:Toolbar name="toolbar">
<uie:ToolbarButton text="Run" name="button-run" style="margin-left: 2px;"/> <uie:ToolbarButton text="Run" name="button-run" style="margin-left: 2px;"/>
<uie:ToolbarButton text="Clear" name="button-clear"/> <uie:ToolbarButton text="Clear" name="button-clear"/>

View file

@ -9,6 +9,19 @@ A tool to help validate data across scenes, prefabs and scriptable objects.
![Hierachy Window](~Images/editor-hierarchy-window.png) ![Hierachy Window](~Images/editor-hierarchy-window.png)
![Project Window](~Images/editor-project-window.png) ![Project Window](~Images/editor-project-window.png)
## Component Validators
```
public sealed class ComponentValidatorMeshCollider : IComponentValidator<MeshCollider>
{
public void Validate(MeshCollider component, List<ValidatorResult> results)
{
if (component.sharedMesh == null)
results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh"));
}
}
```
## Attribute Validators ## Attribute Validators
The field attrribute: The field attrribute:

View file

@ -1,29 +0,0 @@
using System;
namespace Module.ProjectValidator
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class RequiredAttribute : Attribute
{
private readonly EValidatorSeverity _severity;
public RequiredAttribute()
{
_severity = EValidatorSeverity.Error;
}
public RequiredAttribute(EValidatorSeverity severity)
{
_severity = severity;
}
public sealed class Validator : IAttributeValidator<RequiredAttribute>
{
public ValidatorResult Validate(RequiredAttribute attribute, object value)
{
var isValid = value is UnityEngine.Object obj ? obj != null : value != null;
return isValid ? ValidatorResult.Valid : ValidatorResult.Create(attribute._severity, "Value is Null");
}
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 752bd3df42129e14cadaf285192455f4

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using UnityEngine;
namespace Module.ProjectValidator
{
public interface IComponentValidator<in T> where T : Component
{
void Validate(T component, List<ValidatorResult> results);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 48d9acd22b7744faa4b826f26311b5b3
timeCreated: 1779123421

View file

@ -1,6 +1,6 @@
{ {
"name": "com.module.project-validator", "name": "com.module.project-validator",
"version": "0.1.0", "version": "0.2.0",
"displayName": "Module.ProjectValidator", "displayName": "Module.ProjectValidator",
"description": "", "description": "",
"unity": "6000.3", "unity": "6000.3",