diff --git a/Editor/Objects/Report.cs b/Editor/Objects/Report.cs index dae4e6c..aa6d136 100644 --- a/Editor/Objects/Report.cs +++ b/Editor/Objects/Report.cs @@ -13,21 +13,21 @@ namespace Module.ProjectValidator.Editor private readonly Dictionary _assetToSeverityMapping = new(); private readonly Dictionary _instanceToSeverityMapping = new(); - public void Add(GUID assetGuid, string scenePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) + public void Add(GUID assetGuid, string relativePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) { var type = ProjectValidatorUtility.GetAttributeShortName(attribute); - Add(assetGuid, scenePath, fieldPath, type, severity, message); + Add(assetGuid, relativePath, fieldPath, type, severity, message); } - public void Add(GUID assetGuid, string scenePath, string fieldPath, string type, EValidatorSeverity severity, string message) + public void Add(GUID assetGuid, string relativePath, string fieldPath, string type, EValidatorSeverity severity, string message) { Entries.Add(new Entry { AssetGuid = assetGuid, AssetName = EditorAssetUtility.GetAssetName(assetGuid), - ScenePath = scenePath, + RelativePath = relativePath, FieldPath = fieldPath, - ScenePathRichText = ProjectValidatorUtility.ApplyRichTextToScenePath(scenePath), + RelativePathRichText = ProjectValidatorUtility.ApplyRichTextToRelativePath(relativePath), FieldPathRichText = ProjectValidatorUtility.ApplyRichTextToFieldPath(fieldPath), Type = type, Severity = severity, @@ -74,7 +74,7 @@ namespace Module.ProjectValidator.Editor return false; } - public bool TryGetSeverityFor(GUID assetGuid, string scenePath, out MappingEntry mapping) + public bool TryGetSeverityFor(GUID assetGuid, string relativePath, out MappingEntry mapping) { if (!_assetToSeverityMapping.TryGetValue(assetGuid, out mapping)) return false; @@ -83,7 +83,7 @@ namespace Module.ProjectValidator.Editor for (var i = 0; i < Entries.Count; i++) { - if (Entries[i].AssetGuid != assetGuid || Entries[i].ScenePath != scenePath || Entries[i].Severity <= mapping.Severity) + if (Entries[i].AssetGuid != assetGuid || Entries[i].RelativePath != relativePath || Entries[i].Severity <= mapping.Severity) continue; mapping = new MappingEntry(Entries[i].Severity, false); @@ -110,10 +110,10 @@ namespace Module.ProjectValidator.Editor public GUID AssetGuid; public string AssetName; - public string ScenePath; + public string RelativePath; public string FieldPath; - public string ScenePathRichText; + public string RelativePathRichText; public string FieldPathRichText; public string Type; @@ -124,7 +124,7 @@ namespace Module.ProjectValidator.Editor public bool Filter(string filter) { return AssetName.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || - ScenePath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || + RelativePath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || FieldPath.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || Type.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || SeverityResult.Contains(filter, StringComparison.InvariantCultureIgnoreCase) || diff --git a/Editor/Utilities/EditorAssetUtility.cs b/Editor/Utilities/EditorAssetUtility.cs index fc956b4..be7796a 100644 --- a/Editor/Utilities/EditorAssetUtility.cs +++ b/Editor/Utilities/EditorAssetUtility.cs @@ -31,20 +31,21 @@ namespace Module.ProjectValidator.Editor internal static GUID GetAssetGuid(Object obj) { - var assetGuid = new GUID(); - + var assetPath = string.Empty; + if (obj is GameObject gameObject) { if (gameObject.scene.isLoaded) - GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid); + assetPath = gameObject.scene.path; else if (PrefabUtility.IsPartOfPrefabAsset(gameObject)) - GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid); + assetPath = AssetDatabase.GetAssetPath(gameObject); } else { - GUID.TryParse(AssetDatabase.GetAssetPath(obj), out assetGuid); + assetPath = AssetDatabase.GetAssetPath(obj); } + GUID.TryParse(AssetDatabase.AssetPathToGUID(assetPath), out var assetGuid); return assetGuid; } diff --git a/Editor/Utilities/ProjectValidatorUtility.cs b/Editor/Utilities/ProjectValidatorUtility.cs index 92eb95b..f9f520c 100644 --- a/Editor/Utilities/ProjectValidatorUtility.cs +++ b/Editor/Utilities/ProjectValidatorUtility.cs @@ -62,14 +62,17 @@ namespace Module.ProjectValidator.Editor return str; } - internal static void AppendToScenePath(GameObject gameObject, ref string scenePath) + internal static void AppendToRelativePath(GameObject gameObject, ref string relativePath, bool initial) { - scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}"; + if (string.IsNullOrEmpty(relativePath)) + relativePath = gameObject.name; + else + relativePath = initial ? $"{relativePath}{gameObject.name}" : $"{relativePath}/{gameObject.name}"; } - internal static string ApplyRichTextToScenePath(string scenePath) + internal static string ApplyRichTextToRelativePath(string relativePath) { - return scenePath.Replace("/", "/"); + return relativePath.Replace("/", "/"); } public static void AppendToFieldPath(FieldInfo fieldInfo, ref string fieldPath) @@ -105,7 +108,7 @@ namespace Module.ProjectValidator.Editor { var scene = SceneManager.GetSceneByPath(assetPath); - if (scene.isLoaded && TryFindSceneObjectByPath(scene, entry.ScenePath, out var gameObject)) + if (scene.isLoaded && TryFindSceneObjectByPath(scene, entry.RelativePath, out var gameObject)) EditorGUIUtility.PingObject(gameObject); else EditorGUIUtility.PingObject(asset); @@ -116,14 +119,14 @@ namespace Module.ProjectValidator.Editor } } - private static bool TryFindSceneObjectByPath(Scene scene, string scenePath, out GameObject gameObject) + private static bool TryFindSceneObjectByPath(Scene scene, string relativePath, out GameObject gameObject) { using var _ = ListPool.Get(out var rootObjects); scene.GetRootGameObjects(rootObjects); - var index = scenePath.IndexOf('/'); - var rootName = index != -1 ? scenePath[..index] : scenePath; - var childPath = index != -1 ? scenePath[(index + 1)..] : string.Empty; + var index = relativePath.IndexOf('/'); + var rootName = index != -1 ? relativePath[..index] : relativePath; + var childPath = index != -1 ? relativePath[(index + 1)..] : string.Empty; for (var i = 0; i < rootObjects.Count; i++) { @@ -212,25 +215,25 @@ namespace Module.ProjectValidator.Editor for (var j = 0; j < rootObjects.Count; j++) { var rootObject = rootObjects[j]; - var scenePath = string.Empty; - RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, scenePath); + 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 scenePath) + private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) { var transform = gameObject.transform; - AppendToScenePath(gameObject, ref scenePath); + AppendToRelativePath(gameObject, ref relativePath, initial); - if (report.TryGetSeverityFor(assetGuid, scenePath, out var mapping)) + if (report.TryGetSeverityFor(assetGuid, relativePath, 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, scenePath); + RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, relativePath, false); } } diff --git a/Editor/ValidatorRunner.cs b/Editor/ValidatorRunner.cs index f992f35..85786a4 100644 --- a/Editor/ValidatorRunner.cs +++ b/Editor/ValidatorRunner.cs @@ -30,6 +30,7 @@ namespace Module.ProjectValidator.Editor var report = new Report(); ValidateAllScenes(report); ValidateAllAssets(report); + ValidateAllPrefabs(report); report.RebuildAssetMapping(); report.RebuildInstanceMapping(); report.SetAsActive(); @@ -155,7 +156,7 @@ namespace Module.ProjectValidator.Editor for (var j = 0; j < rootObjects.Count; j++) { - ValidateGameObject(rootObjects[j], string.Empty, report); + ValidateGameObject(rootObjects[j], "scene:", report, true); } if (!isLoaded) @@ -173,6 +174,23 @@ namespace Module.ProjectValidator.Editor ValidateAssetsBytype(report); } + private static void ValidateAllPrefabs(Report report) + { + var assets = EditorAssetUtility.LoadAllAssets(); + + for (var i = 0; i < assets.Length; i++) + { + try + { + ValidateGameObject(assets[i], "prefab:", report, true); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + private static void ValidateAssetsBytype(Report report) where T : UnityEngine.Object { var assets = EditorAssetUtility.LoadAllAssets(); @@ -193,12 +211,13 @@ namespace Module.ProjectValidator.Editor private static void ValidateUnityObject(UnityEngine.Object obj, Report report) { var assetGuid = EditorAssetUtility.ObjectToAssetGuid(obj); - Validate(assetGuid, string.Empty, obj, report); + var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); + Validate(assetGuid, $"asset:{assetPath}", obj, report); } - private static void ValidateGameObject(GameObject gameObject, string scenePath, Report report) + private static void ValidateGameObject(GameObject gameObject, string relativePath, Report report, bool initial) { - ProjectValidatorUtility.AppendToScenePath(gameObject, ref scenePath); + ProjectValidatorUtility.AppendToRelativePath(gameObject, ref relativePath, initial); var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); using var _ = ListPool.Get(out var results); @@ -215,15 +234,15 @@ namespace Module.ProjectValidator.Editor var result = results[j]; if (result.Severity != EValidatorSeverity.Valid) - report.Add(assetGuid, scenePath, string.Empty, type, result.Severity, result.Message); + report.Add(assetGuid, relativePath, string.Empty, type, result.Severity, result.Message); } } - ValidateComponents(gameObject, assetGuid, scenePath, report); - ValidateChildren(gameObject, scenePath, report); + ValidateComponents(gameObject, assetGuid, relativePath, report); + ValidateChildren(gameObject, relativePath, report); } - private static void ValidateComponents(GameObject gameObject, GUID assetGuid, string scenePath, Report report) + private static void ValidateComponents(GameObject gameObject, GUID assetGuid, string relativePath, Report report) { using var _ = ListPool.Get(out var components); gameObject.GetComponents(components); @@ -231,21 +250,21 @@ namespace Module.ProjectValidator.Editor for (var i = 0; i < components.Count; i++) { if (components[i] != null) - Validate(assetGuid, scenePath, components[i], report); + Validate(assetGuid, relativePath, components[i], report); } } - private static void ValidateChildren(GameObject gameObject, string scenePath, Report report) + 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, scenePath, report); + ValidateGameObject(transform.GetChild(i).gameObject, relativePath, report, false); } } - private static void Validate(GUID assetGuid, string scenePath, object obj, Report report) + private static void Validate(GUID assetGuid, string relativePath, object obj, Report report) { var type = obj.GetType(); @@ -253,10 +272,10 @@ namespace Module.ProjectValidator.Editor return; var fieldPath = obj.GetType().Name; - Validate(assetGuid, scenePath, fieldPath, obj, entry, report); + Validate(assetGuid, relativePath, fieldPath, obj, entry, report); } - private static void Validate(GUID assetGuid, string scenePath, string parentFieldPath, object obj, TypeTree.Entry entry, Report report) + private static void Validate(GUID assetGuid, string relativePath, string parentFieldPath, object obj, TypeTree.Entry entry, Report report) { if (obj == null) return; @@ -268,7 +287,7 @@ namespace Module.ProjectValidator.Editor try { var component = entry.Components[i]; - ValidateComponent(component, obj, assetGuid, scenePath, report); + ValidateComponent(component, obj, assetGuid, relativePath, report); } catch (Exception e) { @@ -297,13 +316,13 @@ namespace Module.ProjectValidator.Editor { var fieldPathArrElement = fieldPath; ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); - ValidateField(field, eObj, assetGuid, scenePath, fieldPathArrElement, report); + ValidateField(field, eObj, assetGuid, relativePath, fieldPathArrElement, report); idx++; } } else { - ValidateField(field, value, assetGuid, scenePath, fieldPath, report); + ValidateField(field, value, assetGuid, relativePath, fieldPath, report); } } catch (Exception e) @@ -333,13 +352,13 @@ namespace Module.ProjectValidator.Editor { var fieldPathArrElement = fieldPath; ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); - Validate(assetGuid, scenePath, fieldPathArrElement, eObj, e.Entry, report); + Validate(assetGuid, relativePath, fieldPathArrElement, eObj, e.Entry, report); idx++; } } else { - Validate(assetGuid, scenePath, fieldPath, value, e.Entry, report); + Validate(assetGuid, relativePath, fieldPath, value, e.Entry, report); } } catch (Exception e) @@ -350,15 +369,15 @@ namespace Module.ProjectValidator.Editor } } - private static void ValidateField(TypeTree.ValidatorField field, object value, GUID assetGuid, string scenePath, string fieldPath, Report report) + 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, scenePath, fieldPath, field.Attribute, result.Severity, result.Message); + report.Add(assetGuid, relativePath, fieldPath, field.Attribute, result.Severity, result.Message); } - private static void ValidateComponent(TypeTree.ValidatorComponent component, object value, GUID assetGuid, string scenePath, Report report) + 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 }); @@ -369,7 +388,7 @@ namespace Module.ProjectValidator.Editor var result = results[i]; if (result.Severity != EValidatorSeverity.Valid) - report.Add(assetGuid, scenePath, string.Empty, type, result.Severity, result.Message); + report.Add(assetGuid, relativePath, string.Empty, type, result.Severity, result.Message); } } } diff --git a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs new file mode 100644 index 0000000..1ea0509 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +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) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"GameObject has duplicate '{type.Name}' ({count}) components")); + + type = t; + count = 1; + } + } + + if (count > 1) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"GameObject has duplicate '{type.Name}' ({count}) components")); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta new file mode 100644 index 0000000..9947888 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorDuplicateComponents.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b27a4e96523d4d3d97c11b32814f29d3 +timeCreated: 1779213834 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs b/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs new file mode 100644 index 0000000..d6be9b0 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..9a9d755 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorObsoleteComponents.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ca678bbf72fa4c8f8c9b945535aacf44 +timeCreated: 1779214145 \ No newline at end of file diff --git a/Editor/Window/EditorProjectValidatorWindow.cs b/Editor/Window/EditorProjectValidatorWindow.cs index 1a1451c..4989d7d 100644 --- a/Editor/Window/EditorProjectValidatorWindow.cs +++ b/Editor/Window/EditorProjectValidatorWindow.cs @@ -30,14 +30,14 @@ namespace Module.ProjectValidator.Editor _treeView.columns["type"].makeCell = CreateLabel; _treeView.columns["severity"].makeCell = CreateLabel; _treeView.columns["severity-message"].makeCell = CreateLabel; - _treeView.columns["scene-path"].makeCell = CreateLabel; + _treeView.columns["relative-path"].makeCell = CreateLabel; _treeView.columns["field-path"].makeCell = CreateLabel; _treeView.columns["asset"].bindCell = OnTreeViewBindCellAsset; _treeView.columns["type"].bindCell = OnTreeViewBindCellType; _treeView.columns["severity"].bindCell = OnTreeViewBindCellSeverity; _treeView.columns["severity-message"].bindCell = OnTreeViewBindCellSeverityMessage; - _treeView.columns["scene-path"].bindCell = OnTreeViewBindCellScenePath; + _treeView.columns["relative-path"].bindCell = OnTreeViewBindCellRelativePath; _treeView.columns["field-path"].bindCell = OnTreeViewBindCellFieldPath; _treeView.columns["severity"].unbindCell = OnTreeViewUnbindCellSeverity; @@ -46,7 +46,7 @@ namespace Module.ProjectValidator.Editor _treeView.columns["type"].comparison = OnTreeViewComparisonCellType; _treeView.columns["severity"].comparison = OnTreeViewComparisonCellSeverity; _treeView.columns["severity-message"].comparison = OnTreeViewComparisonCellSeverityMessage; - _treeView.columns["scene-path"].comparison = OnTreeViewComparisonCellScenePath; + _treeView.columns["relative-path"].comparison = OnTreeViewComparisonCellRelativePath; _treeView.columns["field-path"].comparison = OnTreeViewComparisonCellFieldPath; _treeView.selectionChanged += OnTreeViewSelectionChanged; @@ -170,11 +170,11 @@ namespace Module.ProjectValidator.Editor label.text = entry.SeverityResult; } - private void OnTreeViewBindCellScenePath(VisualElement ve, int index) + private void OnTreeViewBindCellRelativePath(VisualElement ve, int index) { var label = (Label)ve; var entry = _treeView.GetItemDataForIndex(index); - label.text = entry.ScenePathRichText; + label.text = entry.RelativePathRichText; } private void OnTreeViewBindCellFieldPath(VisualElement ve, int index) @@ -222,11 +222,11 @@ namespace Module.ProjectValidator.Editor return string.Compare(entry0.SeverityResult, entry1.SeverityResult, StringComparison.Ordinal); } - private int OnTreeViewComparisonCellScenePath(int index0, int index1) + private int OnTreeViewComparisonCellRelativePath(int index0, int index1) { var entry0 = _treeView.GetItemDataForIndex(index0); var entry1 = _treeView.GetItemDataForIndex(index1); - return string.Compare(entry0.ScenePath, entry1.ScenePath, StringComparison.Ordinal); + return string.Compare(entry0.RelativePath, entry1.RelativePath, StringComparison.Ordinal); } private int OnTreeViewComparisonCellFieldPath(int index0, int index1) diff --git a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml index 1573eb8..f06f29c 100644 --- a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml +++ b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml @@ -10,7 +10,7 @@ - + diff --git a/package.json b/package.json index a9663df..1a71e3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.module.project-validator", - "version": "0.3.0", + "version": "0.4.0", "displayName": "Module.ProjectValidator", "description": "", "unity": "6000.3",