diff --git a/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs b/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs index cacd52c..a41dbc3 100644 --- a/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs +++ b/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs @@ -10,32 +10,19 @@ namespace Module.ProjectValidator.Editor { static EditorProjectValidatorHierarchy() { -#if UNITY_6000_4_OR_NEWER - EditorApplication.hierarchyWindowItemByEntityIdOnGUI -= OnHierarchyWindowItemByEntityIdOnGUI; - EditorApplication.hierarchyWindowItemByEntityIdOnGUI += OnHierarchyWindowItemByEntityIdOnGUI; -#else EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemOnGUI; EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemOnGUI; -#endif EditorSceneManager.sceneOpened -= OnSceneOpened; EditorSceneManager.sceneOpened += OnSceneOpened; } -#if UNITY_6000_4_OR_NEWER - private static void OnHierarchyWindowItemByEntityIdOnGUI(EntityId entityId, Rect selectionRect) - { - if (Report.HasActive && Report.Active.TryGetSeverityFor(entityId, out var instance) && instance.Severity != EValidatorSeverity.Valid) - EditorIconUtility.Draw(new Rect(selectionRect.x, selectionRect.y, selectionRect.height, selectionRect.height), instance.Severity, instance.IsRedirect); - } -#else private static void OnHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect) { if (Report.HasActive && Report.Active.TryGetSeverityFor(instanceID, out var instance) && instance.Severity != EValidatorSeverity.Valid) EditorIconUtility.Draw(new Rect(selectionRect.x, selectionRect.y, selectionRect.height, selectionRect.height), instance.Severity, instance.IsRedirect); } -#endif - + private static void OnSceneOpened(Scene scene, OpenSceneMode mode) { if (Report.HasActive && ProjectValidatorUtility.IsValidForRun()) diff --git a/Editor/Icons/editor_project_validator_settings.png b/Editor/Icons/editor_project_validator_settings.png deleted file mode 100644 index f938577..0000000 Binary files a/Editor/Icons/editor_project_validator_settings.png and /dev/null differ diff --git a/Editor/Icons/editor_project_validator_settings.png.meta b/Editor/Icons/editor_project_validator_settings.png.meta deleted file mode 100644 index 524687d..0000000 --- a/Editor/Icons/editor_project_validator_settings.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: a6c61d5fbd310894d8159ba6af32d7e3 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 0 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 1 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Objects.meta b/Editor/Objects.meta deleted file mode 100644 index 9c34aa0..0000000 --- a/Editor/Objects.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3cf6340ace4c49589cc0467648d03ee7 -timeCreated: 1778923205 \ No newline at end of file diff --git a/Editor/Objects/TypeTree.cs b/Editor/Objects/TypeTree.cs deleted file mode 100644 index 4cddc30..0000000 --- a/Editor/Objects/TypeTree.cs +++ /dev/null @@ -1,216 +0,0 @@ -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 fieldAttributes = fi.GetCustomAttributes(); - var fieldTypeAttributes = fi.FieldType.GetCustomAttributes(); - - foreach (var attribute in fieldAttributes) - { - if (validatorList.TryGetAttributeValidator(attribute.GetType(), out var validator)) - entry.AddField(fi, attribute, validator); - } - - foreach (var attribute in fieldTypeAttributes) - { - 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/Objects/ValidatorList.cs b/Editor/Objects/ValidatorList.cs deleted file mode 100644 index 39221ff..0000000 --- a/Editor/Objects/ValidatorList.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -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 readonly List GameObjectValidators = new(); - public readonly Dictionary> AssetValidators = 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 void AddGameObject(Type type) - { - if (type.IsInterface || type.IsAbstract) - return; - - try - { - var instance = (IGameObjectValidator)FormatterServices.GetUninitializedObject(type); - GameObjectValidators.Add(instance); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - - public void AddAsset(Type type) - { - if (type.IsInterface || type.IsAbstract) - return; - - var typeValidator = type.GetInterfaces().FirstOrDefault(typeInterface => typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(IAssetValidator<>)); - var componentType = typeValidator?.GetGenericArguments()[0]; - - if (componentType == null) - return; - - try - { - var instance = FormatterServices.GetUninitializedObject(type); - var validator = new AssetValidator(instance); - - if (AssetValidators.TryGetValue(componentType, out var list)) - list.Add(validator); - else - AssetValidators.Add(componentType, new List { validator }); - } - 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); - } - - public sealed class AssetValidator - { - public readonly object Validator; - public readonly MethodInfo ValidatorMethod; - - public AssetValidator(object validator) - { - Validator = validator; - ValidatorMethod = validator.GetType().GetMethod("Validate"); - } - } - } -} \ No newline at end of file diff --git a/Editor/Settings/ProjectValidatorSettings.cs b/Editor/Settings/ProjectValidatorSettings.cs index ec2893f..b51372b 100644 --- a/Editor/Settings/ProjectValidatorSettings.cs +++ b/Editor/Settings/ProjectValidatorSettings.cs @@ -1,36 +1,27 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.UIElements; using UnityEditorInternal; -using UnityEngine.Pool; -using Object = UnityEngine.Object; namespace Module.ProjectValidator.Editor { internal sealed class ProjectValidatorSettings : ScriptableObject { public List assemblies = new(); - [NonReorderable] - public List validators = new(); - - public const string MenuPath = "Project/Project Validator"; + private const string AssetPath = "ProjectSettings/ProjectValidatorSettings.asset"; private const string StyleSheetName = "StyleSheetProjectValidatorSettings"; internal static ProjectValidatorSettings GetOrCreate() { var objects = InternalEditorUtility.LoadSerializedFileAndForget(AssetPath); - ProjectValidatorSettings settings; if (objects.Length != 0) - settings = (ProjectValidatorSettings)objects[0]; - else - settings = CreateInstance(); + return (ProjectValidatorSettings)objects[0]; - PopulateValidatorList(settings); + var settings = CreateInstance(); InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { settings }, AssetPath, true); return settings; } @@ -38,7 +29,7 @@ namespace Module.ProjectValidator.Editor [SettingsProvider] public static SettingsProvider CreateProvider() { - return new SettingsProvider(MenuPath, SettingsScope.Project) + return new SettingsProvider("Project/Project Validator", SettingsScope.Project) { label = "Project Validator", activateHandler = (_, root) => @@ -46,79 +37,15 @@ namespace Module.ProjectValidator.Editor var settings = GetOrCreate(); var serializedObject = new SerializedObject(settings); var container = new VisualElement { style = { flexDirection = FlexDirection.Column } }; - - var assemblyField = new PropertyField(serializedObject.FindProperty(nameof(assemblies)), "Assemblies"); - container.Add(assemblyField); - - var enabledField = new PropertyField(serializedObject.FindProperty(nameof(validators)), "Validators"); - container.Add(enabledField); - + var propertyField = new PropertyField(serializedObject.FindProperty("assemblies"), "Assemblies"); + propertyField.RegisterCallback>(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); + container.Add(propertyField); root.Add(container); root.Bind(serializedObject); - root.RegisterCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); - root.RegisterCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); root.styleSheets.Add(EditorAssetUtility.LoadFirstAsset(StyleSheetName)); }, keywords = new HashSet(new[] { "Project", "Validator", "Assemblies" }) }; } - - private static void PopulateValidatorList(ProjectValidatorSettings settings) - { - using var pool0 = ListPool.Get(out var list); - using var pool1 = ListPool.Get(out var temp); - - FetchValidatorsOfType(typeof(IAssetValidator<>), list); - FetchValidatorsOfType(typeof(IAttributeValidator<>), list); - FetchValidatorsOfType(typeof(IComponentValidator<>), list); - FetchValidatorsOfType(typeof(IGameObjectValidator), list); - - for (var i = 0; i < settings.validators.Count; i++) - { - temp.Add(settings.validators[i].assemblyQualifiedName); - } - - for (var i = 0; i < list.Count; i++) - { - if (temp.Contains(list[i])) - continue; - - var type = Type.GetType(list[i]); - var name = type != null ? type.Name : "Unknown Type"; - settings.validators.Add(new ValidatorEnabled(name, list[i], true)); - } - - for (var i = temp.Count - 1; i >= 0; i--) - { - if (!list.Contains(temp[i])) - settings.validators.RemoveAt(i); - } - } - - private static void FetchValidatorsOfType(Type type, List typeNames) - { - var types = TypeCache.GetTypesDerivedFrom(type); - - for (var i = 0; i < types.Count; i++) - { - if (!types[i].IsInterface && !types[i].IsAbstract) - typeNames.Add(types[i].AssemblyQualifiedName); - } - } - - [Serializable] - public sealed class ValidatorEnabled - { - public string name; - public string assemblyQualifiedName; - public bool enabled; - - public ValidatorEnabled(string name, string assemblyQualifiedName, bool enabled) - { - this.name = name; - this.assemblyQualifiedName = assemblyQualifiedName; - this.enabled = enabled; - } - } } } \ No newline at end of file diff --git a/Editor/Utilities/EditorAssetUtility.cs b/Editor/Utilities/EditorAssetUtility.cs index e3c0ea4..a18be5e 100644 --- a/Editor/Utilities/EditorAssetUtility.cs +++ b/Editor/Utilities/EditorAssetUtility.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; -using Object = UnityEngine.Object; namespace Module.ProjectValidator.Editor { @@ -11,7 +9,7 @@ namespace Module.ProjectValidator.Editor { public static T LoadFirstAsset(string name) where T : Object { - var guids = AssetDatabase.FindAssetGUIDs($"t:{typeof(T).Name} {name}"); + var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{typeof(T).Name} {name}"); return guids.Length != 0 ? AssetDatabase.LoadAssetByGUID(guids[0]) : null; } @@ -31,39 +29,22 @@ namespace Module.ProjectValidator.Editor return list.ToArray(); } - public static Object[] LoadAllAssets(Type type) - { - var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{type.Name}"); - var list = new List(guids.Length); - - foreach (var guid in guids) - { - var asset = AssetDatabase.LoadAssetByGUID(guid, type); - - if (asset != null) - list.Add(asset); - } - - return list.ToArray(); - } - internal static GUID GetAssetGuid(Object obj) { - var assetPath = string.Empty; - + var assetGuid = new GUID(); + if (obj is GameObject gameObject) { if (gameObject.scene.isLoaded) - assetPath = gameObject.scene.path; + GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid); else if (PrefabUtility.IsPartOfPrefabAsset(gameObject)) - assetPath = AssetDatabase.GetAssetPath(gameObject); + GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid); } else { - assetPath = AssetDatabase.GetAssetPath(obj); + GUID.TryParse(AssetDatabase.GetAssetPath(obj), out assetGuid); } - GUID.TryParse(AssetDatabase.AssetPathToGUID(assetPath), out var assetGuid); return assetGuid; } diff --git a/Editor/Utilities/ProjectValidatorUtility.cs b/Editor/Utilities/ProjectValidatorUtility.cs index 10d6eef..c276853 100644 --- a/Editor/Utilities/ProjectValidatorUtility.cs +++ b/Editor/Utilities/ProjectValidatorUtility.cs @@ -37,50 +37,27 @@ namespace Module.ProjectValidator.Editor window.titleContent = new GUIContent("Project Validator"); return window; } - - public static string GetAssetValidatorName(object validator) - { - var str = validator.GetType().Name; - str = str.Replace("AssetValidator", string.Empty); - str = ObjectNames.NicifyVariableName(str); - return str; - } - - internal static string GetGameObjectValidatorName(IGameObjectValidator validator) - { - var str = validator.GetType().Name; - str = str.Replace("GameObjectValidator", 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; - } internal static string GetAttributeShortName(Attribute attribute) { var str = attribute.GetType().Name; - str = str.Replace("Attribute", string.Empty); + var index = str.IndexOf("Attribute", StringComparison.Ordinal); + + if (index != -1) + str = str[..index]; + str = ObjectNames.NicifyVariableName(str); return str; } - - internal static void AppendToRelativePath(GameObject gameObject, ref string relativePath, bool initial) + + internal static void AppendToScenePath(GameObject gameObject, ref string scenePath) { - if (string.IsNullOrEmpty(relativePath)) - relativePath = gameObject.name; - else - relativePath = initial ? $"{relativePath}{gameObject.name}" : $"{relativePath}/{gameObject.name}"; + scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}"; } - internal static string ApplyRichTextToRelativePath(string relativePath) + internal static string ApplyRichTextToScenePath(string scenePath) { - return relativePath.Replace("/", "/"); + return scenePath.Replace("/", "/"); } public static void AppendToFieldPath(FieldInfo fieldInfo, ref string fieldPath) @@ -116,7 +93,7 @@ namespace Module.ProjectValidator.Editor { var scene = SceneManager.GetSceneByPath(assetPath); - if (scene.isLoaded && TryFindSceneObjectByPath(scene, entry.RelativePath, out var gameObject)) + if (scene.isLoaded && TryFindSceneObjectByPath(scene, entry.ScenePath, out var gameObject)) EditorGUIUtility.PingObject(gameObject); else EditorGUIUtility.PingObject(asset); @@ -127,14 +104,14 @@ namespace Module.ProjectValidator.Editor } } - private static bool TryFindSceneObjectByPath(Scene scene, string relativePath, out GameObject gameObject) + private static bool TryFindSceneObjectByPath(Scene scene, string scenePath, out GameObject gameObject) { using var _ = ListPool.Get(out var rootObjects); scene.GetRootGameObjects(rootObjects); - var index = relativePath.IndexOf('/'); - var rootName = index != -1 ? relativePath[..index] : relativePath; - var childPath = index != -1 ? relativePath[(index + 1)..] : string.Empty; + var index = scenePath.IndexOf('/'); + var rootName = index != -1 ? scenePath[..index] : scenePath; + var childPath = index != -1 ? scenePath[(index + 1)..] : string.Empty; for (var i = 0; i < rootObjects.Count; i++) { @@ -204,93 +181,6 @@ namespace Module.ProjectValidator.Editor } } -#if UNITY_6000_4_OR_NEWER - internal static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping) - { - dictMapping.Clear(); - using var _ = ListPool.Get(out var rootObjects); - - for (var i = 0; i < SceneManager.sceneCount; i++) - { - var scene = SceneManager.GetSceneAt(i); - - if (!scene.isLoaded) - continue; - - var strAssetGuid = AssetDatabase.AssetPathToGUID(scene.path); - GUID.TryParse(strAssetGuid, out var assetGuid); - scene.GetRootGameObjects(rootObjects); - - for (var j = 0; j < rootObjects.Count; j++) - { - var rootObject = rootObjects[j]; - var relativePath = string.Empty; - RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, relativePath, true); - } - } - - RebuildForAllParents(dictMapping); - } - - - private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) - { - var transform = gameObject.transform; - AppendToRelativePath(gameObject, ref relativePath, initial); - - if (report.TryGetSeverityFor(assetGuid, relativePath, out var mapping)) - dictMapping.Add(gameObject.GetEntityId(), new Report.MappingEntry(mapping.Severity, false)); - - for (var i = 0; i < transform.childCount; i++) - { - RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, relativePath, false); - } - } - - private static void RebuildForAllParents(Dictionary dictMapping) - { - using var _ = DictionaryPool.Get(out var newMappings); - - foreach (var pair in dictMapping) - { - var obj = EditorUtility.EntityIdToObject(pair.Key); - - if (obj is not GameObject gameObject) - continue; - - var severity = pair.Value.Severity; - var transform = gameObject.transform.parent; - - while (transform != null) - { - gameObject = transform.gameObject; - var entityId = gameObject.GetEntityId(); - - if (dictMapping.TryGetValue(entityId, out var parentMapping)) - { - if (severity < parentMapping.Severity) - severity = parentMapping.Severity; - } - else if (newMappings.TryGetValue(entityId, out var currentMapping)) - { - if (currentMapping.Severity < severity) - newMappings[entityId] = new Report.MappingEntry(severity, true); - } - else - { - newMappings.Add(entityId, new Report.MappingEntry(severity, true)); - } - - transform = transform.parent; - } - } - - foreach (var pair in newMappings) - { - dictMapping.Add(pair.Key, pair.Value); - } - } -#else internal static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping) { dictMapping.Clear(); @@ -310,26 +200,25 @@ namespace Module.ProjectValidator.Editor for (var j = 0; j < rootObjects.Count; j++) { var rootObject = rootObjects[j]; - var relativePath = string.Empty; - RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, relativePath, true); + var scenePath = string.Empty; + RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, scenePath); } } RebuildForAllParents(dictMapping); } - - private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) + private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string scenePath) { var transform = gameObject.transform; - AppendToRelativePath(gameObject, ref relativePath, initial); + AppendToScenePath(gameObject, ref scenePath); - if (report.TryGetSeverityFor(assetGuid, relativePath, out var mapping)) + if (report.TryGetSeverityFor(assetGuid, scenePath, out var mapping)) dictMapping.Add(gameObject.GetInstanceID(), new Report.MappingEntry(mapping.Severity, false)); for (var i = 0; i < transform.childCount; i++) { - RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, relativePath, false); + RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, scenePath); } } @@ -376,7 +265,6 @@ namespace Module.ProjectValidator.Editor dictMapping.Add(pair.Key, pair.Value); } } -#endif internal static void RefreshUnityWindows() { diff --git a/Editor/ValidatorRunner.cs b/Editor/ValidatorRunner.cs deleted file mode 100644 index b69797a..0000000 --- a/Editor/ValidatorRunner.cs +++ /dev/null @@ -1,485 +0,0 @@ -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 -{ - internal static class ValidatorRunner - { - private static ValidatorList _validatorList; - private static TypeTree _typeTree; - - public static bool Run(bool showWindow = true) - { - if (!ProjectValidatorUtility.IsValidForRun()) - return false; - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - Initialize(); - - var report = new Report(); - ValidateAllScenes(report); - ValidateAllAssets(report); - ValidateAllPrefabs(report); - report.RebuildAssetMapping(); - report.RebuildInstanceMapping(); - report.SetAsActive(); - - ProjectValidatorUtility.RefreshUnityWindows(); - - if (showWindow) - ProjectValidatorUtility.OpenWindow(); - - stopwatch.Stop(); - Debug.Log($"Validator took {stopwatch.Elapsed.TotalMilliseconds}ms"); - return true; - } - - public static void Clear() - { - if (!Report.HasActive) - return; - - Report.ClearActive(); - ProjectValidatorUtility.ClearWindow(); - } - - private static void Initialize() - { - var settings = ProjectValidatorSettings.GetOrCreate(); - var assemblies = GetAssembliesFrom(settings); - var enabled = GetEnabledValidators(settings); - - _validatorList = new ValidatorList(); - _typeTree = new TypeTree(); - - FetchAllGameObjectValidators(enabled); - FetchAllComponentValidators(enabled); - FetchAllAttributeValidators(enabled); - FetchAllAssetValidators(enabled); - FetchAllTypesWithValidators(assemblies); - FetchAllTypesWithValidators(assemblies); - } - - private static Assembly[] GetAssembliesFrom(ProjectValidatorSettings settings) - { - var assemblies = new List(settings.assemblies.Count); - - for (var i = 0; i < settings.assemblies.Count; i++) - { - try - { - var assembly = Assembly.Load(settings.assemblies[i]); - assemblies.Add(assembly); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - - return assemblies.ToArray(); - } - - private static HashSet GetEnabledValidators(ProjectValidatorSettings settings) - { - var enabled = new HashSet(settings.validators.Count); - - for (var i = 0; i < settings.validators.Count; i++) - { - try - { - if (!settings.validators[i].enabled) - continue; - - var type = Type.GetType(settings.validators[i].assemblyQualifiedName); - - if (type != null) - enabled.Add(type); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - - return enabled; - } - - private static void FetchAllAttributeValidators(HashSet enabled) - { - var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>)); - - for (var i = 0; i < types.Count; i++) - { - if (enabled.Contains(types[i])) - _validatorList.AddAttribute(types[i]); - } - } - - private static void FetchAllGameObjectValidators(HashSet enabled) - { - var types = TypeCache.GetTypesDerivedFrom(typeof(IGameObjectValidator)); - - for (var i = 0; i < types.Count; i++) - { - if (enabled.Contains(types[i])) - _validatorList.AddGameObject(types[i]); - } - } - - private static void FetchAllComponentValidators(HashSet enabled) - { - var types = TypeCache.GetTypesDerivedFrom(typeof(IComponentValidator<>)); - - for (var i = 0; i < types.Count; i++) - { - if (enabled.Contains(types[i])) - _validatorList.AddComponent(types[i]); - } - } - - private static void FetchAllAssetValidators(HashSet enabled) - { - var types = TypeCache.GetTypesDerivedFrom(typeof(IAssetValidator<>)); - - for (var i = 0; i < types.Count; i++) - { - if (enabled.Contains(types[i])) - _validatorList.AddAsset(types[i]); - } - } - - private static void FetchAllTypesWithValidators(Assembly[] assemblies) - { - var types = TypeCache.GetTypesDerivedFrom(); - - for (var i = 0; i < types.Count; i++) - { - var type = types[i]; - - if (assemblies.Length == 0 || Array.IndexOf(assemblies, type.Assembly) != -1) - _typeTree.Add(type, _validatorList); - } - } - - private static void ValidateAllScenes(Report report) - { - var assets = EditorAssetUtility.LoadAllAssets(); - var rootObjects = new List(); - - for (var i = 0; i < assets.Length; i++) - { - var assetPath = AssetDatabase.GetAssetPath(assets[i]); - var scene = SceneManager.GetSceneByPath(assetPath); - var isLoaded = scene.isLoaded; - - try - { - if (!isLoaded) - scene = EditorSceneManager.OpenScene(assetPath, OpenSceneMode.Additive); - - scene.GetRootGameObjects(rootObjects); - - for (var j = 0; j < rootObjects.Count; j++) - { - ValidateGameObject(rootObjects[j], string.Empty, report, true); - } - } - catch (Exception e) - { - Debug.LogException(e); - } - finally - { - if (!isLoaded && scene.isLoaded) - EditorSceneManager.CloseScene(scene, true); - } - } - } - - private static void ValidateAllAssets(Report report) - { - ValidateAssetsBytype(report); - - foreach (var pair in _validatorList.AssetValidators) - { - var assets = EditorAssetUtility.LoadAllAssets(pair.Key); - - for (var i = 0; i < assets.Length; i++) - { - try - { - var assetPath = AssetDatabase.GetAssetPath(assets[i]); - var assetGuid = AssetDatabase.GUIDFromAssetPath(assetPath); - ValidateAsset(assets[i], assetGuid, assetPath, pair.Value, report); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - } - - private static void ValidateAllPrefabs(Report report) - { - var assets = EditorAssetUtility.LoadAllAssets(); - - for (var i = 0; i < assets.Length; i++) - { - try - { - ValidateGameObject(assets[i], string.Empty, report, true); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - - private static void ValidateAssetsBytype(Report report) where T : UnityEngine.Object - { - var assets = EditorAssetUtility.LoadAllAssets(); - - for (var i = 0; i < assets.Length; i++) - { - try - { - ValidateUnityObject(assets[i], report); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - - private static void ValidateUnityObject(UnityEngine.Object obj, Report report) - { - var assetGuid = EditorAssetUtility.ObjectToAssetGuid(obj); - var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); - Validate(assetGuid, assetPath, obj, report); - } - - private static void ValidateGameObject(GameObject gameObject, string relativePath, Report report, bool initial) - { - ProjectValidatorUtility.AppendToRelativePath(gameObject, ref relativePath, initial); - - var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); - using var _ = ListPool.Get(out var results); - - for (var i = 0; i < _validatorList.GameObjectValidators.Count; i++) - { - results.Clear(); - - _validatorList.GameObjectValidators[i].Validate(gameObject, results); - var type = ProjectValidatorUtility.GetGameObjectValidatorName(_validatorList.GameObjectValidators[i]); - - for (var j = 0; j < results.Count; j++) - { - var result = results[j]; - - if (result.Severity != EValidatorSeverity.Valid) - report.Add(assetGuid, relativePath, string.Empty, type, result.Severity, result.Message); - } - } - - ValidateComponents(gameObject, assetGuid, relativePath, report); - ValidateChildren(gameObject, relativePath, report); - } - - private static void ValidateComponents(GameObject gameObject, GUID assetGuid, string relativePath, Report report) - { - using var _ = ListPool.Get(out var components); - gameObject.GetComponents(components); - - for (var i = 0; i < components.Count; i++) - { - if (components[i] != null) - Validate(assetGuid, relativePath, components[i], report); - } - } - - private static void ValidateAsset(UnityEngine.Object obj, GUID assetGuid, string relativePath, List validators, Report report) - { - using var _ = ListPool.Get(out var results); - - for (var i = 0; i < validators.Count; i++) - { - results.Clear(); - var validator = validators[i]; - validator.ValidatorMethod.Invoke(validator.Validator, new object[] { obj, results }); - - for (var j = 0; j < results.Count; j++) - { - var result = results[j]; - - if (result.Severity == EValidatorSeverity.Valid) - continue; - - var validatorName = ProjectValidatorUtility.GetAssetValidatorName(validator.Validator); - report.Add(assetGuid, relativePath, string.Empty, validatorName, result.Severity, result.Message); - } - } - } - - private static void ValidateChildren(GameObject gameObject, string relativePath, Report report) - { - var transform = gameObject.transform; - - for (var i = 0; i < transform.childCount; i++) - { - ValidateGameObject(transform.GetChild(i).gameObject, relativePath, report, false); - } - } - - private static void Validate(GUID assetGuid, string relativePath, object obj, Report report) - { - var type = obj.GetType(); - - if (!_typeTree.Types.TryGetValue(type, out var entry)) - return; - - var fieldPath = obj.GetType().Name; - Validate(assetGuid, relativePath, fieldPath, obj, entry, report); - } - - private static void Validate(GUID assetGuid, string relativePath, string parentFieldPath, object obj, TypeTree.Entry entry, Report report) - { - 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, relativePath, report); - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - - if (entry.Fields != null) - { - for (var i = 0; i < entry.Fields.Count; i++) - { - try - { - var field = entry.Fields[i]; - var value = field.FieldInfo.GetValue(obj); - - var fieldPath = parentFieldPath; - ProjectValidatorUtility.AppendToFieldPath(field.FieldInfo, ref fieldPath); - - if (value is IEnumerable ie) - { - var idx = 0; - - foreach (var eObj in ie) - { - var fieldPathArrElement = fieldPath; - ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); - ValidateField(field, eObj, assetGuid, relativePath, fieldPathArrElement, report); - idx++; - } - } - else - { - ValidateField(field, value, assetGuid, relativePath, fieldPath, report); - } - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - - if (entry.Entries != null) - { - for (var i = 0; i < entry.Entries.Count; i++) - { - try - { - var e = entry.Entries[i]; - var value = e.FieldInfo.GetValue(obj); - - var fieldPath = parentFieldPath; - ProjectValidatorUtility.AppendToFieldPath(e.FieldInfo, ref fieldPath); - - if (value is Array arr) - { - for (var j = 0; j < arr.Length; j++) - { - var eObj = arr.GetValue(j); - var fieldPathArrElement = fieldPath; - ProjectValidatorUtility.AppendToFieldPath(j, ref fieldPathArrElement); - Validate(assetGuid, relativePath, fieldPathArrElement, eObj, e.Entry, report); - } - } - else if (value is IEnumerable ie) - { - var idx = 0; - - foreach (var eObj in ie) - { - var fieldPathArrElement = fieldPath; - ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); - Validate(assetGuid, relativePath, fieldPathArrElement, eObj, e.Entry, report); - idx++; - } - } - else - { - Validate(assetGuid, relativePath, fieldPath, value, e.Entry, report); - } - } - catch (Exception e) - { - Debug.LogException(e); - } - } - } - } - - private static void ValidateField(TypeTree.ValidatorField field, object value, GUID assetGuid, string relativePath, string fieldPath, Report report) - { - var result = (ValidatorResult)field.ValidatorMethod.Invoke(field.Validator, new[] { field.Attribute, value }); - - if (result.Severity != EValidatorSeverity.Valid) - report.Add(assetGuid, relativePath, fieldPath, field.Attribute, result.Severity, result.Message); - } - - private static void ValidateComponent(TypeTree.ValidatorComponent component, object value, GUID assetGuid, string relativePath, 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, relativePath, string.Empty, type, result.Severity, result.Message); - } - } - } -} \ No newline at end of file diff --git a/Editor/Validators.meta b/Editor/Validators.meta index e0bfc70..9c34aa0 100644 --- a/Editor/Validators.meta +++ b/Editor/Validators.meta @@ -1,8 +1,3 @@ -fileFormatVersion: 2 -guid: 372e41dfe29c0ef468ae3554d6fbd9f8 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 3cf6340ace4c49589cc0467648d03ee7 +timeCreated: 1778923205 \ No newline at end of file diff --git a/Editor/Validators/Assets.meta b/Editor/Validators/Assets.meta deleted file mode 100644 index a1c08b2..0000000 --- a/Editor/Validators/Assets.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: ddbf30cd5a674751be0c125c1f4e917b -timeCreated: 1779623970 \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs deleted file mode 100644 index f6e072f..0000000 --- a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; -using UnityEngine.Rendering; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class AssetValidatorMaterialShader : IAssetValidator - { - public void Validate(Material obj, List results) - { - if (obj.shader == null) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Shader is Null")); - else if (!IsCompatible(obj.shader)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Shader '{obj.shader.name}' is not compatible with render pipeline")); - else if (!obj.shader.isSupported) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Shader '{obj.shader.name}' is not supported")); - else if (ShaderUtil.ShaderHasError(obj.shader)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Shader '{obj.shader.name}' has compile errors")); - } - - private static bool IsCompatible(Shader shader) - { - var pipeline = GraphicsSettings.currentRenderPipeline; - - if (pipeline == null) - return true; - - var tagSearch = new ShaderTagId("RenderPipeline"); - var tagPipeline = new ShaderTagId(pipeline.renderPipelineShaderTag); - var hasKeyword = false; - - for (var i = 0; i < shader.passCount; i++) - { - var tagPass = shader.FindPassTagValue(i, tagSearch); - - if (tagPass != ShaderTagId.none) - hasKeyword = true; - - if (tagPass == tagPipeline) - return true; - } - - return !hasKeyword; - } - } -} \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta deleted file mode 100644 index a1eceb1..0000000 --- a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 2218d247daff44bf84629756b63ea650 -timeCreated: 1779623986 \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs deleted file mode 100644 index 37097fa..0000000 --- a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Rendering; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class AssetValidatorMaterialTexture : IAssetValidator - { - public void Validate(Material obj, List results) - { - if (obj.shader == null) - return; - - var count = obj.shader.GetPropertyCount(); - - for (var i = 0; i < count; i++) - { - var propertyType = obj.shader.GetPropertyType(i); - var propertyFlags = obj.shader.GetPropertyFlags(i); - - if (propertyType != ShaderPropertyType.Texture) - continue; - if ((propertyFlags & (ShaderPropertyFlags.PerRendererData | ShaderPropertyFlags.HideInInspector | ShaderPropertyFlags.NonModifiableTextureData)) != 0) - continue; - - var propertyName = obj.shader.GetPropertyName(i); - var propertyValue = obj.GetTexture(propertyName); - - if (propertyValue == null) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Texture property '{propertyName}' is Null")); - } - } - } -} \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta deleted file mode 100644 index d975053..0000000 --- a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: cfeb4b29a9cd4ea79c1e9325b8122e17 -timeCreated: 1779627240 \ No newline at end of file diff --git a/Editor/Validators/Attributes.meta b/Editor/Validators/Attributes.meta deleted file mode 100644 index 7b92af6..0000000 --- a/Editor/Validators/Attributes.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0aa1d741049f4074bb863b76ed604d07 -timeCreated: 1779214748 \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorMin.cs b/Editor/Validators/Attributes/AttributeValidatorMin.cs deleted file mode 100644 index 59b7c1e..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorMin.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class AttributeValidatorMin : IAttributeValidator - { - public ValidatorResult Validate(MinAttribute attribute, object value) - { - if (value is int iValue) - { - if (iValue < attribute.min) - return ValidatorResult.Create(EValidatorSeverity.Error, $"Value {iValue} is less than minimum value of {attribute.min}"); - } - else if (value is float fValue) - { - if (fValue < attribute.min) - return ValidatorResult.Create(EValidatorSeverity.Error, $"Value {fValue} is less than minimum value of {attribute.min}"); - } - - return ValidatorResult.Valid; - } - } -} \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorMin.cs.meta b/Editor/Validators/Attributes/AttributeValidatorMin.cs.meta deleted file mode 100644 index 7b95df1..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorMin.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 2015106b3c9b450e9efb1333e5239033 -timeCreated: 1779214761 \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs deleted file mode 100644 index 0eb3c19..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class AttributeValidatorObsolete : IAttributeValidator - { - public ValidatorResult Validate(ObsoleteAttribute attribute, object value) - { - return ValidatorResult.Create(EValidatorSeverity.Error, "Obsolete"); - } - } -} \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta deleted file mode 100644 index 2a54b7b..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 982ac4e898cc2ca438e98a2f0034a8d3 \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorRange.cs b/Editor/Validators/Attributes/AttributeValidatorRange.cs deleted file mode 100644 index b52c1f9..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorRange.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class AttributeValidatorRange : IAttributeValidator - { - public ValidatorResult Validate(RangeAttribute attribute, object value) - { - if (value is int iValue) - { - if (iValue < attribute.min || iValue > attribute.max) - return ValidatorResult.Create(EValidatorSeverity.Error, $"Value {iValue} is not in the range [{attribute.min};{attribute.max}]"); - } - else if (value is float fValue) - { - if (fValue < attribute.min || fValue > attribute.max) - return ValidatorResult.Create(EValidatorSeverity.Error, $"Value {fValue} is not in the range [{attribute.min};{attribute.max}]"); - } - - return ValidatorResult.Valid; - } - } -} \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorRange.cs.meta b/Editor/Validators/Attributes/AttributeValidatorRange.cs.meta deleted file mode 100644 index 531db6b..0000000 --- a/Editor/Validators/Attributes/AttributeValidatorRange.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3115c26bccfe4b1e964f784a3f9fdd55 -timeCreated: 1779214908 \ No newline at end of file diff --git a/Editor/Validators/Component/ComponentValidatorMeshCollider.cs b/Editor/Validators/Component/ComponentValidatorMeshCollider.cs deleted file mode 100644 index bc5b82f..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshCollider.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 3efb6b3..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshCollider.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index c10b32f..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshFilter.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 853e155..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshFilter.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 638f5b6..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 2773482..0000000 --- a/Editor/Validators/Component/ComponentValidatorMeshRenderer.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: faf660447bfedc84f88e1e9809bc6583 \ No newline at end of file diff --git a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs deleted file mode 100644 index ca2d231..0000000 --- a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class ComponentValidatorSkinnedMeshRenderer : IComponentValidator - { - public void Validate(SkinnedMeshRenderer 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}")); - } - - if (component.sharedMesh == null) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Missing mesh")); - } - } -} diff --git a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta b/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta deleted file mode 100644 index 1080734..0000000 --- a/Editor/Validators/Component/ComponentValidatorSkinnedMeshRenderer.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 1610561c53a0aa84aaa903e5dde29694 diff --git a/Editor/Validators/GameObject.meta b/Editor/Validators/GameObject.meta deleted file mode 100644 index 736bf79..0000000 --- a/Editor/Validators/GameObject.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 45fa253dcfa349d5b1e9bd56ebac7c98 -timeCreated: 1779133809 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs deleted file mode 100644 index 8cd9270..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator - { - public void Validate(GameObject gameObject, List results) - { - if (PrefabUtility.IsPrefabAssetMissing(gameObject)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "GameObject is missing prefab asset")); - } - } -} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta deleted file mode 100644 index d2ad050..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorBrokenPrefab.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 8fd1dfcd3d564622a918e2175499318d -timeCreated: 1779133864 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs deleted file mode 100644 index eaf89a1..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; -using UnityEngine.Pool; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class GameObjectValidatorDuplicateComponents : IGameObjectValidator - { - public void Validate(GameObject gameObject, List results) - { - using var _ = ListPool.Get(out var list); - gameObject.GetComponents(list); - list.Sort((c0, c1) => c0.GetType().GetHashCode().CompareTo(c1.GetType().GetHashCode())); - - if (list.Count == 0) - return; - - var type = list[0].GetType(); - var count = 1; - - for (var i = 1; i < list.Count; i++) - { - var t = list[i].GetType(); - - if (type == t) - { - count++; - } - else - { - if (count > 1 && IsMultipleComponentsAllowed(type)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"GameObject has duplicate '{type.Name}' ({count}) components")); - - type = t; - count = 1; - } - } - - if (count > 1 && IsMultipleComponentsAllowed(type)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"GameObject has duplicate '{type.Name}' ({count}) components")); - } - - private static bool IsMultipleComponentsAllowed(Type type) - { - return type.GetCustomAttribute(true) == null; - } - } -} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta deleted file mode 100644 index 9947888..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: b27a4e96523d4d3d97c11b32814f29d3 -timeCreated: 1779213834 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs deleted file mode 100644 index dd43633..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using UnityEditor; -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class GameObjectValidatorMissingComponents : IGameObjectValidator - { - public void Validate(GameObject gameObject, List results) - { - var count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(gameObject); - - if (count != 0) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"GameObject is missing {count} component(s)")); - } - } -} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta deleted file mode 100644 index 47d826d..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorMissingComponents.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 66cbc43729ec4e81a9e353e537b3ccf0 -timeCreated: 1779133947 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs deleted file mode 100644 index d6be9b0..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using UnityEngine; -using UnityEngine.Pool; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class GameObjectValidatorObsoleteComponents : IGameObjectValidator - { - public void Validate(GameObject gameObject, List results) - { - using var _ = ListPool.Get(out var list); - gameObject.GetComponents(list); - - for (var i = 0; i < list.Count; i++) - { - var type = list[i].GetType(); - - if (type.GetCustomAttribute(typeof(ObsoleteAttribute)) != null) - results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"GameObject has obsolete '{type.Name}' component")); - } - } - } -} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs.meta deleted file mode 100644 index 9a9d755..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: ca678bbf72fa4c8f8c9b945535aacf44 -timeCreated: 1779214145 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs deleted file mode 100644 index 58b6f92..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Module.ProjectValidator.Editor -{ - internal sealed class GameObjectValidatorTransform : IGameObjectValidator - { - public void Validate(GameObject gameObject, List results) - { - var transform = gameObject.transform; - var lp = transform.localPosition; - var lr = transform.localRotation; - var ls = transform.localScale; - - if (IsInvalid(lp.x) || IsInvalid(lp.y) || IsInvalid(lp.z)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local position '{lp}' is invalid")); - if (IsInvalid(lr.x) || IsInvalid(lr.y) || IsInvalid(lr.z) || IsInvalid(lr.w)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local rotation '{lr}' is invalid")); - if (IsInvalid(ls.x) || IsInvalid(ls.y) || IsInvalid(ls.z)) - results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local scale '{ls}' is invalid")); - } - - private static bool IsInvalid(float value) - { - return float.IsNaN(value) || float.IsInfinity(value); - } - } -} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta deleted file mode 100644 index 89d7bda..0000000 --- a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 727d5de59b004deb8c192337bcee132e -timeCreated: 1779628852 \ No newline at end of file diff --git a/Editor/Objects/Report.cs b/Editor/Validators/Report.cs similarity index 74% rename from Editor/Objects/Report.cs rename to Editor/Validators/Report.cs index 5b68f9f..e4345be 100644 --- a/Editor/Objects/Report.cs +++ b/Editor/Validators/Report.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using UnityEditor; -using UnityEngine; namespace Module.ProjectValidator.Editor { @@ -12,30 +11,19 @@ namespace Module.ProjectValidator.Editor public readonly List Entries = new(); private readonly Dictionary _assetToSeverityMapping = new(); - -#if UNITY_6000_4_OR_NEWER - private readonly Dictionary _instanceToSeverityMapping = new(); -#else private readonly Dictionary _instanceToSeverityMapping = new(); -#endif - public void Add(GUID assetGuid, string relativePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) - { - var type = ProjectValidatorUtility.GetAttributeShortName(attribute); - Add(assetGuid, relativePath, fieldPath, type, severity, message); - } - - public void Add(GUID assetGuid, string relativePath, string fieldPath, string type, EValidatorSeverity severity, string message) + public void Add(GUID assetGuid, string scenePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) { Entries.Add(new Entry { AssetGuid = assetGuid, AssetName = EditorAssetUtility.GetAssetName(assetGuid), - RelativePath = relativePath, + ScenePath = scenePath, FieldPath = fieldPath, - RelativePathRichText = ProjectValidatorUtility.ApplyRichTextToRelativePath(relativePath), + ScenePathRichText = ProjectValidatorUtility.ApplyRichTextToScenePath(scenePath), FieldPathRichText = ProjectValidatorUtility.ApplyRichTextToFieldPath(fieldPath), - Type = type, + Type = ProjectValidatorUtility.GetAttributeShortName(attribute), Severity = severity, SeverityStr = severity.ToString(), SeverityResult = message @@ -70,17 +58,7 @@ namespace Module.ProjectValidator.Editor mapping = new MappingEntry(); return false; } - -#if UNITY_6000_4_OR_NEWER - public bool TryGetSeverityFor(EntityId entityId, out MappingEntry mapping) - { - if (_instanceToSeverityMapping.TryGetValue(entityId, out mapping)) - return true; - - mapping = new MappingEntry(); - return false; - } -#else + public bool TryGetSeverityFor(int instanceId, out MappingEntry mapping) { if (_instanceToSeverityMapping.TryGetValue(instanceId, out mapping)) @@ -89,9 +67,8 @@ namespace Module.ProjectValidator.Editor mapping = new MappingEntry(); return false; } -#endif - public bool TryGetSeverityFor(GUID assetGuid, string relativePath, out MappingEntry mapping) + public bool TryGetSeverityFor(GUID assetGuid, string scenePath, out MappingEntry mapping) { if (!_assetToSeverityMapping.TryGetValue(assetGuid, out mapping)) return false; @@ -100,7 +77,7 @@ namespace Module.ProjectValidator.Editor for (var i = 0; i < Entries.Count; i++) { - if (Entries[i].AssetGuid != assetGuid || Entries[i].RelativePath != relativePath || Entries[i].Severity <= mapping.Severity) + if (Entries[i].AssetGuid != assetGuid || Entries[i].ScenePath != scenePath || Entries[i].Severity <= mapping.Severity) continue; mapping = new MappingEntry(Entries[i].Severity, false); @@ -127,10 +104,10 @@ namespace Module.ProjectValidator.Editor public GUID AssetGuid; public string AssetName; - public string RelativePath; + public string ScenePath; public string FieldPath; - public string RelativePathRichText; + public string ScenePathRichText; public string FieldPathRichText; public string Type; @@ -141,7 +118,7 @@ namespace Module.ProjectValidator.Editor public bool Filter(string filter) { return AssetName.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || - RelativePath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || + ScenePath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || FieldPath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || Type.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || SeverityResult.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || diff --git a/Editor/Objects/Report.cs.meta b/Editor/Validators/Report.cs.meta similarity index 100% rename from Editor/Objects/Report.cs.meta rename to Editor/Validators/Report.cs.meta diff --git a/Editor/Validators/TypeTree.cs b/Editor/Validators/TypeTree.cs new file mode 100644 index 0000000..155de4e --- /dev/null +++ b/Editor/Validators/TypeTree.cs @@ -0,0 +1,111 @@ +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/Objects/TypeTree.cs.meta b/Editor/Validators/TypeTree.cs.meta similarity index 100% rename from Editor/Objects/TypeTree.cs.meta rename to Editor/Validators/TypeTree.cs.meta diff --git a/Editor/Validators/ValidatorList.cs b/Editor/Validators/ValidatorList.cs new file mode 100644 index 0000000..b37ea35 --- /dev/null +++ b/Editor/Validators/ValidatorList.cs @@ -0,0 +1,40 @@ +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/Objects/ValidatorList.cs.meta b/Editor/Validators/ValidatorList.cs.meta similarity index 100% rename from Editor/Objects/ValidatorList.cs.meta rename to Editor/Validators/ValidatorList.cs.meta diff --git a/Editor/Validators/ValidatorRunner.cs b/Editor/Validators/ValidatorRunner.cs new file mode 100644 index 0000000..46c9a42 --- /dev/null +++ b/Editor/Validators/ValidatorRunner.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.Pool; +using UnityEngine.SceneManagement; + +namespace Module.ProjectValidator.Editor +{ + internal static class ValidatorRunner + { + private static bool _initialized; + private static ValidatorList _validatorList; + private static TypeTree _typeTree; + + public static bool Run(bool showWindow = true) + { + if (!ProjectValidatorUtility.IsValidForRun()) + return false; + + Initialize(); + + var report = new Report(); + ValidateAllScenes(report); + ValidateAllAssets(report); + report.RebuildAssetMapping(); + report.RebuildInstanceMapping(); + report.SetAsActive(); + + ProjectValidatorUtility.RefreshUnityWindows(); + + if (showWindow) + ProjectValidatorUtility.OpenWindow(); + + return true; + } + + public static void Clear() + { + if (!Report.HasActive) + return; + + Report.ClearActive(); + ProjectValidatorUtility.ClearWindow(); + } + + private static void Initialize() + { + if (_initialized) + return; + + var settings = ProjectValidatorSettings.GetOrCreate(); + var assemblies = GetAssembliesFrom(settings); + + _validatorList = new ValidatorList(); + _typeTree = new TypeTree(); + + FetchAllValidators(); + FetchAllTypesWithValidators(assemblies); + FetchAllTypesWithValidators(assemblies); + _initialized = true; + } + + private static Assembly[] GetAssembliesFrom(ProjectValidatorSettings settings) + { + var assemblies = new List(settings.assemblies.Count); + + for (var i = 0; i < settings.assemblies.Count; i++) + { + try + { + var assembly = Assembly.Load(settings.assemblies[i]); + assemblies.Add(assembly); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + return assemblies.ToArray(); + } + + private static void FetchAllValidators() + { + var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>)); + + for (var i = 0; i < types.Count; i++) + { + _validatorList.Add(types[i]); + } + } + + private static void FetchAllTypesWithValidators(Assembly[] assemblies) + { + var types = TypeCache.GetTypesDerivedFrom(); + + for (var i = 0; i < types.Count; i++) + { + var type = types[i]; + + if (Array.IndexOf(assemblies, type.Assembly) != -1) + _typeTree.Add(type, _validatorList); + } + } + + private static void ValidateAllScenes(Report report) + { + var assets = EditorAssetUtility.LoadAllAssets(); + var rootObjects = new List(); + + for (var i = 0; i < assets.Length; i++) + { + try + { + var assetPath = AssetDatabase.GetAssetPath(assets[i]); + var scene = SceneManager.GetSceneByPath(assetPath); + var isLoaded = scene.isLoaded; + + if (!isLoaded) + scene = EditorSceneManager.OpenScene(assetPath, OpenSceneMode.Additive); + + scene.GetRootGameObjects(rootObjects); + + for (var j = 0; j < rootObjects.Count; j++) + { + ValidateGameObject(rootObjects[j], string.Empty, report); + } + + if (!isLoaded) + EditorSceneManager.CloseScene(scene, true); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + private static void ValidateAllAssets(Report report) + { + ValidateAssetsBytype(report); + } + + private static void ValidateAssetsBytype(Report report) where T : UnityEngine.Object + { + var assets = EditorAssetUtility.LoadAllAssets(); + + for (var i = 0; i < assets.Length; i++) + { + try + { + ValidateUnityObject(assets[i], report); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + private static void ValidateUnityObject(UnityEngine.Object obj, Report report) + { + var assetGuid = EditorAssetUtility.ObjectToAssetGuid(obj); + Validate(assetGuid, string.Empty, obj, report); + } + + private static void ValidateGameObject(GameObject gameObject, string scenePath, Report report) + { + ProjectValidatorUtility.AppendToScenePath(gameObject, ref scenePath); + ValidateComponents(gameObject, scenePath, report); + ValidateChildren(gameObject, scenePath, report); + } + + private static void ValidateComponents(GameObject gameObject, string scenePath, Report report) + { + using var _ = ListPool.Get(out var components); + var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); + gameObject.GetComponents(components); + + for (var i = 0; i < components.Count; i++) + { + Validate(assetGuid, scenePath, components[i], report); + } + } + + private static void ValidateChildren(GameObject gameObject, string scenePath, Report report) + { + var transform = gameObject.transform; + + for (var i = 0; i < transform.childCount; i++) + { + ValidateGameObject(transform.GetChild(i).gameObject, scenePath, report); + } + } + + private static void Validate(GUID assetGuid, string scenePath, object obj, Report report) + { + var type = obj.GetType(); + + if (!_typeTree.Types.TryGetValue(type, out var entry)) + return; + + var fieldPath = obj.GetType().Name; + Validate(assetGuid, scenePath, fieldPath, obj, entry, report); + } + + private static void Validate(GUID assetGuid, string scenePath, string parentFieldPath, object obj, TypeTree.Entry entry, Report report) + { + if (obj == null) + return; + + if (entry.Fields != null) + { + for (var i = 0; i < entry.Fields.Count; i++) + { + try + { + var field = entry.Fields[i]; + var value = field.FieldInfo.GetValue(obj); + + var fieldPath = parentFieldPath; + ProjectValidatorUtility.AppendToFieldPath(field.FieldInfo, ref fieldPath); + + if (value is IEnumerable ie) + { + var idx = 0; + + foreach (var eObj in ie) + { + var fieldPathArrElement = fieldPath; + ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); + ValidateField(field, eObj, assetGuid, scenePath, fieldPathArrElement, report); + idx++; + } + } + else + { + ValidateField(field, value, assetGuid, scenePath, fieldPath, report); + } + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + if (entry.Entries != null) + { + for (var i = 0; i < entry.Entries.Count; i++) + { + try + { + var e = entry.Entries[i]; + var value = e.FieldInfo.GetValue(obj); + + var fieldPath = parentFieldPath; + ProjectValidatorUtility.AppendToFieldPath(e.FieldInfo, ref fieldPath); + + if (value is IEnumerable ie) + { + var idx = 0; + + foreach (var eObj in ie) + { + var fieldPathArrElement = fieldPath; + ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); + Validate(assetGuid, scenePath, fieldPathArrElement, eObj, e.Entry, report); + idx++; + } + } + else + { + Validate(assetGuid, scenePath, fieldPath, value, e.Entry, report); + } + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + } + + private static void ValidateField(TypeTree.ValidatorField field, object value, GUID assetGuid, string scenePath, string fieldPath, Report report) + { + var result = (ValidatorResult)field.ValidatorMethod.Invoke(field.Validator, new[] { field.Attribute, value }); + + if (result.Severity != EValidatorSeverity.Valid) + report.Add(assetGuid, scenePath, fieldPath, field.Attribute, result.Severity, result.Message); + } + } +} \ No newline at end of file diff --git a/Editor/ValidatorRunner.cs.meta b/Editor/Validators/ValidatorRunner.cs.meta similarity index 100% rename from Editor/ValidatorRunner.cs.meta rename to Editor/Validators/ValidatorRunner.cs.meta diff --git a/Editor/Window/EditorProjectValidatorWindow.cs b/Editor/Window/EditorProjectValidatorWindow.cs index 1d01017..e5d2d6a 100644 --- a/Editor/Window/EditorProjectValidatorWindow.cs +++ b/Editor/Window/EditorProjectValidatorWindow.cs @@ -8,52 +8,35 @@ namespace Module.ProjectValidator.Editor { internal sealed class EditorProjectValidatorWindow : EditorWindow { - private VisualElement _groupWarnings; - private Label _labelWarnings; - - private VisualElement _groupErrors; - private Label _labelErrors; - private MultiColumnTreeView _treeView; private string _searchFilter; - + private readonly List> _list = new(); private readonly List> _filteredList = new(); - + public void CreateGUI() { var root = rootVisualElement; var asset = EditorAssetUtility.LoadFirstAsset("UxmlEditorProjectValidatorWindow"); - root.styleSheets.Add(EditorAssetUtility.LoadFirstAsset("StyleSheetEditorProjectValidatorWindow")); root.Add(asset.Instantiate()); - _groupWarnings = root.Q("status-warnings"); - _labelWarnings = _groupWarnings.Q