From d7145ad772892109b3dec990dc38c8c541d4f301 Mon Sep 17 00:00:00 2001 From: Anders Ejlersen Date: Mon, 18 May 2026 20:37:05 +0200 Subject: [PATCH] Added component validators --- Editor/Objects.meta | 3 + Editor/{Validators => Objects}/Report.cs | 8 +- Editor/{Validators => Objects}/Report.cs.meta | 0 Editor/Objects/TypeTree.cs | 209 ++++++++++++++++++ .../{Validators => Objects}/TypeTree.cs.meta | 0 Editor/Objects/ValidatorList.cs | 72 ++++++ .../ValidatorList.cs.meta | 0 Editor/Settings/ProjectValidatorSettings.cs | 1 + Editor/Utilities/EditorAssetUtility.cs | 2 +- Editor/Utilities/ProjectValidatorUtility.cs | 14 +- Editor/{Validators => }/ValidatorRunner.cs | 59 ++++- .../{Validators => }/ValidatorRunner.cs.meta | 0 Editor/Validators.meta | 11 +- .../Validators/Component.meta | 2 +- .../ComponentValidatorMeshCollider.cs | 14 ++ .../ComponentValidatorMeshCollider.cs.meta | 3 + .../Component/ComponentValidatorMeshFilter.cs | 14 ++ .../ComponentValidatorMeshFilter.cs.meta | 3 + .../ComponentValidatorMeshRenderer.cs | 19 ++ .../ComponentValidatorMeshRenderer.cs.meta | 2 + Editor/Validators/TypeTree.cs | 111 ---------- Editor/Validators/ValidatorList.cs | 40 ---- Editor/Window/EditorProjectValidatorWindow.cs | 1 + .../UxmlEditorProjectValidatorWindow.uxml | 1 - README.md | 13 ++ Runtime/Attributes/RequiredAttribute.cs | 29 --- Runtime/Attributes/RequiredAttribute.cs.meta | 2 - Runtime/Interfaces/IComponentValidator.cs | 10 + .../Interfaces/IComponentValidator.cs.meta | 3 + package.json | 2 +- 30 files changed, 448 insertions(+), 200 deletions(-) create mode 100644 Editor/Objects.meta rename Editor/{Validators => Objects}/Report.cs (93%) rename Editor/{Validators => Objects}/Report.cs.meta (100%) create mode 100644 Editor/Objects/TypeTree.cs rename Editor/{Validators => Objects}/TypeTree.cs.meta (100%) create mode 100644 Editor/Objects/ValidatorList.cs rename Editor/{Validators => Objects}/ValidatorList.cs.meta (100%) rename Editor/{Validators => }/ValidatorRunner.cs (82%) rename Editor/{Validators => }/ValidatorRunner.cs.meta (100%) rename Runtime/Attributes.meta => Editor/Validators/Component.meta (77%) create mode 100644 Editor/Validators/Component/ComponentValidatorMeshCollider.cs create mode 100644 Editor/Validators/Component/ComponentValidatorMeshCollider.cs.meta create mode 100644 Editor/Validators/Component/ComponentValidatorMeshFilter.cs create mode 100644 Editor/Validators/Component/ComponentValidatorMeshFilter.cs.meta create mode 100644 Editor/Validators/Component/ComponentValidatorMeshRenderer.cs create mode 100644 Editor/Validators/Component/ComponentValidatorMeshRenderer.cs.meta delete mode 100644 Editor/Validators/TypeTree.cs delete mode 100644 Editor/Validators/ValidatorList.cs delete mode 100644 Runtime/Attributes/RequiredAttribute.cs delete mode 100644 Runtime/Attributes/RequiredAttribute.cs.meta create mode 100644 Runtime/Interfaces/IComponentValidator.cs create mode 100644 Runtime/Interfaces/IComponentValidator.cs.meta diff --git a/Editor/Objects.meta b/Editor/Objects.meta new file mode 100644 index 0000000..9c34aa0 --- /dev/null +++ b/Editor/Objects.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3cf6340ace4c49589cc0467648d03ee7 +timeCreated: 1778923205 \ No newline at end of file diff --git a/Editor/Validators/Report.cs b/Editor/Objects/Report.cs similarity index 93% rename from Editor/Validators/Report.cs rename to Editor/Objects/Report.cs index e4345be..dae4e6c 100644 --- a/Editor/Validators/Report.cs +++ b/Editor/Objects/Report.cs @@ -14,6 +14,12 @@ namespace Module.ProjectValidator.Editor private readonly Dictionary _instanceToSeverityMapping = new(); 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 { @@ -23,7 +29,7 @@ namespace Module.ProjectValidator.Editor FieldPath = fieldPath, ScenePathRichText = ProjectValidatorUtility.ApplyRichTextToScenePath(scenePath), FieldPathRichText = ProjectValidatorUtility.ApplyRichTextToFieldPath(fieldPath), - Type = ProjectValidatorUtility.GetAttributeShortName(attribute), + Type = type, Severity = severity, SeverityStr = severity.ToString(), SeverityResult = message diff --git a/Editor/Validators/Report.cs.meta b/Editor/Objects/Report.cs.meta similarity index 100% rename from Editor/Validators/Report.cs.meta rename to Editor/Objects/Report.cs.meta diff --git a/Editor/Objects/TypeTree.cs b/Editor/Objects/TypeTree.cs new file mode 100644 index 0000000..5b69e0c --- /dev/null +++ b/Editor/Objects/TypeTree.cs @@ -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 Types = new(); + private readonly HashSet _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).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() == 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).IsAssignableFrom(type)) + { + var args = type.GetGenericArguments(); + + if (args.Length != 1 || !IsTypeSerializable(args[0])) + return false; + } + + return type.GetCustomAttribute() != null; + } + + public sealed class Entry + { + public List Components; + public List Fields; + public List Entries; + + public void AddComponents(List components) + { + Components ??= new List(); + + 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(); + Fields.Add(new ValidatorField(fieldInfo, attribute, validator)); + } + + public void AddField(FieldInfo fieldInfo, Entry entry) + { + Entries ??= new List(); + 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; + } + } + } +} \ No newline at end of file diff --git a/Editor/Validators/TypeTree.cs.meta b/Editor/Objects/TypeTree.cs.meta similarity index 100% rename from Editor/Validators/TypeTree.cs.meta rename to Editor/Objects/TypeTree.cs.meta diff --git a/Editor/Objects/ValidatorList.cs b/Editor/Objects/ValidatorList.cs new file mode 100644 index 0000000..d75a061 --- /dev/null +++ b/Editor/Objects/ValidatorList.cs @@ -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 _attributeValidators = new(); + private readonly Dictionary> _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 { 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 validatorInstances) + { + return _componentValidators.TryGetValue(type, out validatorInstances); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/ValidatorList.cs.meta b/Editor/Objects/ValidatorList.cs.meta similarity index 100% rename from Editor/Validators/ValidatorList.cs.meta rename to Editor/Objects/ValidatorList.cs.meta diff --git a/Editor/Settings/ProjectValidatorSettings.cs b/Editor/Settings/ProjectValidatorSettings.cs index b51372b..b526db7 100644 --- a/Editor/Settings/ProjectValidatorSettings.cs +++ b/Editor/Settings/ProjectValidatorSettings.cs @@ -39,6 +39,7 @@ namespace Module.ProjectValidator.Editor var container = new VisualElement { style = { flexDirection = FlexDirection.Column } }; var propertyField = new PropertyField(serializedObject.FindProperty("assemblies"), "Assemblies"); propertyField.RegisterCallback>(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); + propertyField.RegisterValueChangeCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); container.Add(propertyField); root.Add(container); root.Bind(serializedObject); diff --git a/Editor/Utilities/EditorAssetUtility.cs b/Editor/Utilities/EditorAssetUtility.cs index a18be5e..fc956b4 100644 --- a/Editor/Utilities/EditorAssetUtility.cs +++ b/Editor/Utilities/EditorAssetUtility.cs @@ -9,7 +9,7 @@ namespace Module.ProjectValidator.Editor { public static T LoadFirstAsset(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(guids[0]) : null; } diff --git a/Editor/Utilities/ProjectValidatorUtility.cs b/Editor/Utilities/ProjectValidatorUtility.cs index c276853..c1f0c2f 100644 --- a/Editor/Utilities/ProjectValidatorUtility.cs +++ b/Editor/Utilities/ProjectValidatorUtility.cs @@ -41,11 +41,15 @@ namespace Module.ProjectValidator.Editor internal static string GetAttributeShortName(Attribute attribute) { var str = attribute.GetType().Name; - var index = str.IndexOf("Attribute", StringComparison.Ordinal); - - if (index != -1) - str = str[..index]; - + str = str.Replace("Attribute", string.Empty); + str = ObjectNames.NicifyVariableName(str); + return str; + } + + internal static string GetComponentValidatorShortName(object obj) + { + var str = obj.GetType().Name; + str = str.Replace("ComponentValidator", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } diff --git a/Editor/Validators/ValidatorRunner.cs b/Editor/ValidatorRunner.cs similarity index 82% rename from Editor/Validators/ValidatorRunner.cs rename to Editor/ValidatorRunner.cs index 46c9a42..af9cdd4 100644 --- a/Editor/Validators/ValidatorRunner.cs +++ b/Editor/ValidatorRunner.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Pool; using UnityEngine.SceneManagement; +using Debug = UnityEngine.Debug; namespace Module.ProjectValidator.Editor { @@ -19,6 +21,9 @@ namespace Module.ProjectValidator.Editor { if (!ProjectValidatorUtility.IsValidForRun()) return false; + + var stopwatch = new Stopwatch(); + stopwatch.Start(); Initialize(); @@ -34,6 +39,8 @@ namespace Module.ProjectValidator.Editor if (showWindow) ProjectValidatorUtility.OpenWindow(); + stopwatch.Stop(); + Debug.Log(stopwatch.Elapsed.TotalMilliseconds + "ms"); return true; } @@ -57,8 +64,9 @@ namespace Module.ProjectValidator.Editor _validatorList = new ValidatorList(); _typeTree = new TypeTree(); - FetchAllValidators(); - FetchAllTypesWithValidators(assemblies); + FetchAllAttributeValidators(); + FetchAllComponentValidators(); + FetchAllTypesWithValidators(assemblies); FetchAllTypesWithValidators(assemblies); _initialized = true; } @@ -83,13 +91,23 @@ namespace Module.ProjectValidator.Editor return assemblies.ToArray(); } - private static void FetchAllValidators() + private static void FetchAllAttributeValidators() { var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>)); 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]; - if (Array.IndexOf(assemblies, type.Assembly) != -1) + if (assemblies.Length == 0 || Array.IndexOf(assemblies, type.Assembly) != -1) _typeTree.Add(type, _validatorList); } } @@ -211,6 +229,22 @@ namespace Module.ProjectValidator.Editor { if (obj == null) 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) { @@ -292,5 +326,20 @@ namespace Module.ProjectValidator.Editor if (result.Severity != EValidatorSeverity.Valid) 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.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); + } + } } } \ No newline at end of file diff --git a/Editor/Validators/ValidatorRunner.cs.meta b/Editor/ValidatorRunner.cs.meta similarity index 100% rename from Editor/Validators/ValidatorRunner.cs.meta rename to Editor/ValidatorRunner.cs.meta diff --git a/Editor/Validators.meta b/Editor/Validators.meta index 9c34aa0..e0bfc70 100644 --- a/Editor/Validators.meta +++ b/Editor/Validators.meta @@ -1,3 +1,8 @@ -fileFormatVersion: 2 -guid: 3cf6340ace4c49589cc0467648d03ee7 -timeCreated: 1778923205 \ No newline at end of file +fileFormatVersion: 2 +guid: 372e41dfe29c0ef468ae3554d6fbd9f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Attributes.meta b/Editor/Validators/Component.meta similarity index 77% rename from Runtime/Attributes.meta rename to Editor/Validators/Component.meta index ed11b68..3207a39 100644 --- a/Runtime/Attributes.meta +++ b/Editor/Validators/Component.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b550bfeed6d6bb840972d41daa80bbd3 +guid: e2f0308e61e26cd468af9c290586f6fb folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Editor/Validators/Component/ComponentValidatorMeshCollider.cs b/Editor/Validators/Component/ComponentValidatorMeshCollider.cs new file mode 100644 index 0000000..bc5b82f --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshCollider.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class ComponentValidatorMeshCollider : IComponentValidator + { + public void Validate(MeshCollider component, List results) + { + if (component.sharedMesh == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh")); + } + } +} diff --git a/Editor/Validators/Component/ComponentValidatorMeshCollider.cs.meta b/Editor/Validators/Component/ComponentValidatorMeshCollider.cs.meta new file mode 100644 index 0000000..3efb6b3 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshCollider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b42427573c2a4e68a9d3cc76773972c7 +timeCreated: 1779125565 \ No newline at end of file diff --git a/Editor/Validators/Component/ComponentValidatorMeshFilter.cs b/Editor/Validators/Component/ComponentValidatorMeshFilter.cs new file mode 100644 index 0000000..c10b32f --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshFilter.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class ComponentValidatorMeshFilter : IComponentValidator + { + public void Validate(MeshFilter component, List results) + { + if (component.sharedMesh == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh")); + } + } +} diff --git a/Editor/Validators/Component/ComponentValidatorMeshFilter.cs.meta b/Editor/Validators/Component/ComponentValidatorMeshFilter.cs.meta new file mode 100644 index 0000000..853e155 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshFilter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e232459fff2b408aae26a2b1178e9b9f +timeCreated: 1779125497 \ No newline at end of file diff --git a/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs b/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs new file mode 100644 index 0000000..638f5b6 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class ComponentValidatorMeshRenderer : IComponentValidator + { + public void Validate(MeshRenderer component, List 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}")); + } + } + } +} diff --git a/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs.meta b/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs.meta new file mode 100644 index 0000000..2773482 --- /dev/null +++ b/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: faf660447bfedc84f88e1e9809bc6583 \ No newline at end of file diff --git a/Editor/Validators/TypeTree.cs b/Editor/Validators/TypeTree.cs deleted file mode 100644 index 155de4e..0000000 --- a/Editor/Validators/TypeTree.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class TypeTree - { - public readonly Dictionary 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).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 Fields; - public List Entries; - - public void Add(FieldInfo fieldInfo, Attribute attribute, object validator) - { - Fields ??= new List(); - Fields.Add(new ValidatorField(fieldInfo, attribute, validator)); - } - - public void Add(FieldInfo fieldInfo, Entry entry) - { - Entries ??= new List(); - 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; - } - } - } -} \ No newline at end of file diff --git a/Editor/Validators/ValidatorList.cs b/Editor/Validators/ValidatorList.cs deleted file mode 100644 index b37ea35..0000000 --- a/Editor/Validators/ValidatorList.cs +++ /dev/null @@ -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 _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); - } - } -} \ No newline at end of file diff --git a/Editor/Window/EditorProjectValidatorWindow.cs b/Editor/Window/EditorProjectValidatorWindow.cs index e5d2d6a..1a1451c 100644 --- a/Editor/Window/EditorProjectValidatorWindow.cs +++ b/Editor/Window/EditorProjectValidatorWindow.cs @@ -18,6 +18,7 @@ namespace Module.ProjectValidator.Editor { var root = rootVisualElement; var asset = EditorAssetUtility.LoadFirstAsset("UxmlEditorProjectValidatorWindow"); + root.styleSheets.Add(EditorAssetUtility.LoadFirstAsset("StyleSheetEditorProjectValidatorWindow")); root.Add(asset.Instantiate()); root.Q("button-run").clicked += OnToolbarButtonRunClicked; diff --git a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml index 6e9309d..1573eb8 100644 --- a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml +++ b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml @@ -1,5 +1,4 @@ -