├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── Documentation~ ├── Images │ └── ValidatorWindow_01.png └── Validator.md ├── Editor.meta ├── Editor ├── IValidator.cs ├── IValidator.cs.meta ├── ValidatorEditorWindow.cs ├── ValidatorEditorWindow.cs.meta ├── Validators.meta ├── Validators │ ├── RequiredAttributeValidator.cs │ ├── RequiredAttributeValidator.cs.meta │ ├── ValidatableAssetValidator.cs │ ├── ValidatableAssetValidator.cs.meta │ ├── ValidatableSceneValidator.cs │ └── ValidatableSceneValidator.cs.meta ├── VertexColor.Validator.Editor.asmdef └── VertexColor.Validator.Editor.asmdef.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── IValidatable.cs ├── IValidatable.cs.meta ├── Report.cs ├── Report.cs.meta ├── ReportCategories.cs ├── ReportCategories.cs.meta ├── Validators.meta ├── Validators │ ├── Attributes.meta │ └── Attributes │ │ ├── RequiredAttribute.cs │ │ └── RequiredAttribute.cs.meta ├── VertexColor.Validator.asmdef └── VertexColor.Validator.asmdef.meta ├── THIRD PARTY NOTICES.md ├── THIRD PARTY NOTICES.md.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | [Bb]uild/ 5 | [Bb]uilds/ 6 | [Ll]ogs/ 7 | Assets/AssetStoreTools* 8 | 9 | # Visual Studio 2015 cache directory 10 | /.vs/ 11 | 12 | # Autogenerated VS/MD/Consulo solution and project files 13 | ExportedObj/ 14 | .consulo/ 15 | *.csproj 16 | *.unityproj 17 | *.sln 18 | *.suo 19 | *.tmp 20 | *.user 21 | *.userprefs 22 | *.pidb 23 | *.booproj 24 | *.svd 25 | *.pdb 26 | 27 | # Unity3D generated meta files 28 | *.pidb.meta 29 | *.pdb.meta 30 | 31 | # Unity3D Generated File On Crash Reports 32 | sysinfo.txt 33 | 34 | # Builds 35 | *.apk 36 | *.unitypackage -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log: 2 | ## 0.1.4 3 | - Fix attribute validator..? 4 | ## 0.1.3 5 | - Fix attribute validator..? 6 | ## 0.1.2 7 | - ... 8 | ## 0.1.1 9 | - First prototype of Attribute validator. 10 | ## 0.1.1 11 | - Search input. 12 | - GUI toolbar. 13 | - WarningType filter. 14 | ## 0.1.0 15 | - Base validator framework. -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 12174dcb2c74c6f4eb0f6d28d8670c14 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Documentation~/Images/ValidatorWindow_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxartz15/Validator/5b70e24c3226858428e08169b5060e74e26255e7/Documentation~/Images/ValidatorWindow_01.png -------------------------------------------------------------------------------- /Documentation~/Validator.md: -------------------------------------------------------------------------------- 1 | # Validator 2 | 3 | Documentation. -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27a6e9d765504c6449f6cb83c81f2353 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/IValidator.cs: -------------------------------------------------------------------------------- 1 | namespace Validator.Editor 2 | { 3 | public interface IValidator 4 | { 5 | public string MenuName { get; } 6 | public Report Validate(); 7 | } 8 | } -------------------------------------------------------------------------------- /Editor/IValidator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75a64c845d92d1548bb095b40a6f5c9f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ValidatorEditorWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | using UnityEditor; 6 | using UnityEditor.IMGUI.Controls; 7 | using Object = UnityEngine.Object; 8 | 9 | namespace Validator.Editor 10 | { 11 | public class ValidatorEditorWindow : EditorWindow 12 | { 13 | private class ValidatorInfo 14 | { 15 | public IValidator validator = null; 16 | public bool isEnabled = true; 17 | 18 | public ValidatorInfo(IValidator validator) 19 | { 20 | this.validator = validator; 21 | } 22 | } 23 | 24 | private class ReportStats 25 | { 26 | public int infoCount = 0; 27 | public int warningCount = 0; 28 | public int errorCount = 0; 29 | } 30 | 31 | private class Settings 32 | { 33 | public bool showInfo = true; 34 | public bool showWarning = true; 35 | public bool showError = true; 36 | public string searchInput = ""; 37 | 38 | public readonly Color lightColor = new Color(0.25f, 0.25f, 0.25f, 1); 39 | public readonly Color darkColor = new Color(0.22f, 0.22f, 0.22f, 1); 40 | } 41 | 42 | private static readonly List validators = new List(); 43 | private static readonly List reports = new List(); 44 | 45 | private static readonly Settings settings = new Settings(); 46 | private static ReportStats reportStats = new ReportStats(); 47 | 48 | private MultiColumnHeaderState multiColumnHeaderState; 49 | private MultiColumnHeader multiColumnHeader; 50 | private MultiColumnHeaderState.Column[] columns; 51 | private Vector2 scrollPosition; 52 | private float multiColumnHeaderWidth; 53 | private float rows; 54 | 55 | [MenuItem("Window/General/Validator")] 56 | private static void Init() 57 | { 58 | ValidatorEditorWindow window = (ValidatorEditorWindow)GetWindow(typeof(ValidatorEditorWindow)); 59 | window.titleContent = new GUIContent("Validator", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image); 60 | window.Show(); 61 | window.LoadValidators(); 62 | } 63 | 64 | private void OnGUI() 65 | { 66 | using (new EditorGUILayout.VerticalScope(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))) 67 | { 68 | DrawMenu(); 69 | GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); 70 | DrawMultiColumnScope(); 71 | } 72 | } 73 | 74 | private void InitializeMultiColumn() 75 | { 76 | columns = new MultiColumnHeaderState.Column[] 77 | { 78 | new MultiColumnHeaderState.Column() 79 | { 80 | allowToggleVisibility = false, // At least one column must be there. 81 | autoResize = false, 82 | width = 36f, 83 | minWidth = 36f, 84 | maxWidth = 36f, 85 | canSort = false, 86 | sortingArrowAlignment = TextAlignment.Right, 87 | headerContent = EditorGUIUtility.IconContent("d_console.erroricon.inactive.sml", "Warning Type."), 88 | headerTextAlignment = TextAlignment.Center, 89 | }, 90 | new MultiColumnHeaderState.Column() 91 | { 92 | allowToggleVisibility = true, 93 | autoResize = false, 94 | width = 36f, 95 | minWidth = 36f, 96 | maxWidth = 36f, 97 | canSort = false, 98 | sortingArrowAlignment = TextAlignment.Right, 99 | headerContent = EditorGUIUtility.IconContent("Animation.FilterBySelection", "Target Objects"), 100 | headerTextAlignment = TextAlignment.Center, 101 | }, 102 | new MultiColumnHeaderState.Column() 103 | { 104 | allowToggleVisibility = true, 105 | autoResize = true, 106 | width = 75.0f, 107 | minWidth = 75.0f, 108 | canSort = false, 109 | sortingArrowAlignment = TextAlignment.Right, 110 | headerContent = new GUIContent("Category", "Warning Category."), 111 | headerTextAlignment = TextAlignment.Center, 112 | }, 113 | new MultiColumnHeaderState.Column() 114 | { 115 | allowToggleVisibility = true, 116 | autoResize = true, 117 | width = 200.0f, 118 | minWidth = 200.0f, 119 | canSort = false, 120 | sortingArrowAlignment = TextAlignment.Right, 121 | headerContent = new GUIContent("Message", "Warning Message."), 122 | headerTextAlignment = TextAlignment.Center, 123 | }, 124 | new MultiColumnHeaderState.Column() 125 | { 126 | allowToggleVisibility = true, 127 | autoResize = true, 128 | width = 200.0f, 129 | minWidth = 200.0f, 130 | canSort = false, 131 | sortingArrowAlignment = TextAlignment.Right, 132 | headerContent = new GUIContent("Solution", "Warning Solution."), 133 | headerTextAlignment = TextAlignment.Center, 134 | }, 135 | }; 136 | 137 | multiColumnHeaderState = new MultiColumnHeaderState(columns); 138 | multiColumnHeader = new MultiColumnHeader(multiColumnHeaderState); 139 | multiColumnHeader.ResizeToFit(); 140 | } 141 | 142 | private void DrawMenu() 143 | { 144 | using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar)) 145 | { 146 | if (GUILayout.Button(EditorGUIUtility.IconContent("Refresh", "Refresh"), EditorStyles.toolbarButton)) 147 | { 148 | LoadValidators(); 149 | } 150 | 151 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_Settings", "Select validator"), EditorStyles.toolbarButton)) 152 | { 153 | GenericMenu validatorOptionsMenu = new GenericMenu(); 154 | validatorOptionsMenu.AddItem(new GUIContent($"All"), false, OnValidatorInfoVisibilityAllEvent); 155 | validatorOptionsMenu.AddItem(new GUIContent($"None"), false, OnValidatorInfoVisibilityNoneEvent); 156 | validatorOptionsMenu.AddSeparator(""); 157 | foreach (ValidatorInfo validatorInfo in validators) 158 | { 159 | validatorOptionsMenu.AddItem(new GUIContent($"{validatorInfo.validator.MenuName}"), validatorInfo.isEnabled, OnValidatorInfoVisibilityChangedEvent, validatorInfo); 160 | } 161 | validatorOptionsMenu.ShowAsContext(); 162 | } 163 | 164 | if (GUILayout.Button(EditorGUIUtility.IconContent("PlayButton", "Run all"), EditorStyles.toolbarButton)) 165 | { 166 | RunValidators(); 167 | UpdateStats(); 168 | } 169 | 170 | GUILayout.FlexibleSpace(); 171 | 172 | settings.searchInput = GUILayout.TextField(settings.searchInput, EditorStyles.toolbarSearchField, GUILayout.MaxWidth(300)); 173 | 174 | if(reportStats != null) 175 | { 176 | settings.showInfo = GUILayout.Toggle(settings.showInfo, new GUIContent($"{reportStats.infoCount}", EditorGUIUtility.IconContent(GetWarningIconName(WarningType.Info)).image), EditorStyles.toolbarButton); 177 | settings.showWarning = GUILayout.Toggle(settings.showWarning, new GUIContent($"{reportStats.warningCount}", EditorGUIUtility.IconContent(GetWarningIconName(WarningType.Warning)).image), EditorStyles.toolbarButton); 178 | settings.showError = GUILayout.Toggle(settings.showError, new GUIContent($"{reportStats.errorCount}", EditorGUIUtility.IconContent(GetWarningIconName(WarningType.Error)).image), EditorStyles.toolbarButton); 179 | } 180 | } 181 | } 182 | 183 | private void DrawMultiColumnScope() 184 | { 185 | //GUILayout.FlexibleSpace(); // If nothing has been drawn yet, uncomment this because GUILayoutUtility.GetLastRect() needs it. 186 | Rect windowRect = GUILayoutUtility.GetLastRect(); 187 | windowRect.width = position.width; 188 | windowRect.height = position.height; 189 | 190 | if (multiColumnHeader == null) 191 | { 192 | InitializeMultiColumn(); 193 | } 194 | 195 | float columnHeight = EditorGUIUtility.singleLineHeight * 2; 196 | 197 | Rect headerRect = new Rect(windowRect) 198 | { 199 | height = EditorGUIUtility.singleLineHeight, 200 | }; 201 | 202 | Rect scrollViewPositionRect = GUILayoutUtility.GetRect(0, float.MaxValue, 0, float.MaxValue); 203 | scrollViewPositionRect.y += headerRect.height - EditorGUIUtility.standardVerticalSpacing; 204 | scrollViewPositionRect.height -= headerRect.height - EditorGUIUtility.standardVerticalSpacing; 205 | 206 | Rect scrollViewRect = new Rect(windowRect) 207 | { 208 | width = multiColumnHeaderState.widthOfAllVisibleColumns, 209 | height = rows * columnHeight 210 | }; 211 | 212 | Rect rowRect = new Rect(windowRect) 213 | { 214 | width = multiColumnHeaderWidth, 215 | height = columnHeight, 216 | }; 217 | 218 | // Draw column header. 219 | multiColumnHeader.OnGUI(headerRect, scrollPosition.x); 220 | 221 | // Draw scroll view. 222 | using (GUI.ScrollViewScope scope = new GUI.ScrollViewScope(scrollViewPositionRect, scrollPosition, scrollViewRect, false, false)) 223 | { 224 | scrollPosition = scope.scrollPosition; 225 | multiColumnHeaderWidth = Mathf.Max(scrollViewPositionRect.width + scrollPosition.x, multiColumnHeaderWidth); 226 | 227 | rows = 0; 228 | for (int i = 0; i < reports.Count; i++) 229 | { 230 | for (int j = 0; j < reports[i].Reports.Count; j++) 231 | { 232 | // Type filter. 233 | switch (reports[i].Reports[j].WarningType) 234 | { 235 | case WarningType.Info: 236 | if (!settings.showInfo) 237 | continue; 238 | break; 239 | case WarningType.Warning: 240 | if (!settings.showWarning) 241 | continue; 242 | break; 243 | case WarningType.Error: 244 | if (!settings.showError) 245 | continue; 246 | break; 247 | default: 248 | break; 249 | } 250 | 251 | // Search filter. 252 | if (!string.IsNullOrWhiteSpace(settings.searchInput)) 253 | { 254 | if (!reports[i].Reports[j].Category.Contains(settings.searchInput) && !reports[i].Reports[j].Message.Contains(settings.searchInput) && !reports[i].Reports[j].Solution.Contains(settings.searchInput)) 255 | { 256 | continue; 257 | } 258 | } 259 | 260 | // Only draw what is visible within the view. 261 | if (rowRect.yMax > windowRect.y + scrollPosition.y && rowRect.yMin < windowRect.height + scrollPosition.y) 262 | { 263 | if (rows % 2 == 0) 264 | { 265 | EditorGUI.DrawRect(rowRect, settings.darkColor); 266 | } 267 | else 268 | { 269 | EditorGUI.DrawRect(rowRect, settings.lightColor); 270 | } 271 | 272 | // Warning Field. 273 | int columnIndex = 0; 274 | if (multiColumnHeader.IsColumnVisible(columnIndex)) 275 | { 276 | int visibleColumnIndex = multiColumnHeader.GetVisibleColumnIndex(columnIndex); 277 | Rect columnRect = multiColumnHeader.GetColumnRect(visibleColumnIndex); 278 | columnRect.y = rowRect.y; 279 | columnRect.height = rowRect.height; 280 | 281 | Rect labelFieldRect = multiColumnHeader.GetCellRect(visibleColumnIndex, columnRect); 282 | labelFieldRect.x += 9; 283 | labelFieldRect.width -= 9; 284 | 285 | EditorGUI.LabelField( 286 | labelFieldRect, 287 | EditorGUIUtility.IconContent(GetWarningIconName(reports[i].Reports[j].WarningType), $"{reports[i].Reports[j].WarningType}") 288 | ); 289 | } 290 | 291 | // Target Field. 292 | columnIndex = 1; 293 | if (multiColumnHeader.IsColumnVisible(columnIndex)) 294 | { 295 | int visibleColumnIndex = multiColumnHeader.GetVisibleColumnIndex(columnIndex); 296 | Rect columnRect = multiColumnHeader.GetColumnRect(visibleColumnIndex); 297 | columnRect.y = rowRect.y; 298 | columnRect.height = rowRect.height; 299 | 300 | if(reports[i].Reports[j].Target != null) 301 | { 302 | if (GUI.Button( 303 | multiColumnHeader.GetCellRect(visibleColumnIndex, columnRect), 304 | EditorGUIUtility.IconContent("Animation.FilterBySelection", "Ping Object") 305 | )) 306 | { 307 | Selection.objects = new Object[] { reports[i].Reports[j].Target }; 308 | 309 | if (!Selection.activeObject) 310 | { 311 | Debug.Log($"{nameof(Selection.activeObject)} is null."); 312 | continue; 313 | } 314 | 315 | foreach (Object o in Selection.objects) 316 | { 317 | EditorGUIUtility.PingObject(o); 318 | } 319 | } 320 | } 321 | } 322 | 323 | // Category Field. 324 | columnIndex = 2; 325 | if (multiColumnHeader.IsColumnVisible(columnIndex)) 326 | { 327 | int visibleColumnIndex = multiColumnHeader.GetVisibleColumnIndex(columnIndex); 328 | Rect columnRect = multiColumnHeader.GetColumnRect(visibleColumnIndex); 329 | columnRect.y = rowRect.y; 330 | columnRect.height = rowRect.height; 331 | 332 | Rect labelFieldRect = multiColumnHeader.GetCellRect(visibleColumnIndex, columnRect); 333 | labelFieldRect.x += 7; 334 | labelFieldRect.width -= 7; 335 | 336 | EditorGUI.LabelField( 337 | labelFieldRect, 338 | new GUIContent($"{reports[i].Reports[j].Category}") 339 | ); 340 | } 341 | 342 | // Message Field. 343 | columnIndex = 3; 344 | if (multiColumnHeader.IsColumnVisible(columnIndex)) 345 | { 346 | int visibleColumnIndex = multiColumnHeader.GetVisibleColumnIndex(columnIndex); 347 | Rect columnRect = multiColumnHeader.GetColumnRect(visibleColumnIndex); 348 | columnRect.y = rowRect.y; 349 | columnRect.height = rowRect.height; 350 | 351 | Rect labelFieldRect = multiColumnHeader.GetCellRect(visibleColumnIndex, columnRect); 352 | labelFieldRect.x += 7; 353 | labelFieldRect.width -= 7; 354 | 355 | EditorGUI.LabelField( 356 | labelFieldRect, 357 | new GUIContent($"{reports[i].Reports[j].Message}") 358 | ); 359 | } 360 | 361 | // Solution Field. 362 | columnIndex = 4; 363 | if (multiColumnHeader.IsColumnVisible(columnIndex)) 364 | { 365 | int visibleColumnIndex = multiColumnHeader.GetVisibleColumnIndex(columnIndex); 366 | Rect columnRect = multiColumnHeader.GetColumnRect(visibleColumnIndex); 367 | columnRect.y = rowRect.y; 368 | columnRect.height = rowRect.height; 369 | 370 | Rect labelFieldRect = multiColumnHeader.GetCellRect(visibleColumnIndex, columnRect); 371 | labelFieldRect.x += 7; 372 | labelFieldRect.width -= 7; 373 | 374 | EditorGUI.LabelField( 375 | labelFieldRect, 376 | new GUIContent($"{reports[i].Reports[j].Solution}") 377 | ); 378 | } 379 | } 380 | 381 | rowRect.y += columnHeight; 382 | rows++; 383 | } 384 | } 385 | scope.handleScrollWheel = true; 386 | } 387 | } 388 | 389 | private void LoadValidators() 390 | { 391 | for (int i = validators.Count - 1; i >= 0; i--) 392 | { 393 | if (validators[i].validator == null) 394 | { 395 | validators.RemoveAt(i); 396 | } 397 | } 398 | 399 | foreach (Type type in GetValidatorTypes()) 400 | { 401 | bool hasValidator = false; 402 | foreach (ValidatorInfo validatorInfo in validators) 403 | { 404 | if (validatorInfo.validator.GetType() == type) 405 | { 406 | hasValidator = true; 407 | } 408 | } 409 | 410 | if (!hasValidator) 411 | { 412 | IValidator validator = (IValidator)Activator.CreateInstance(type); 413 | if (validator != null) 414 | { 415 | validators.Add(new ValidatorInfo(validator)); 416 | } 417 | } 418 | } 419 | } 420 | 421 | private void RunValidators() 422 | { 423 | reports.Clear(); 424 | 425 | foreach (ValidatorInfo validatorInfo in validators) 426 | { 427 | if (validatorInfo.isEnabled) 428 | { 429 | reports.Add(validatorInfo.validator.Validate()); 430 | } 431 | } 432 | } 433 | 434 | private void UpdateStats() 435 | { 436 | reportStats = new ReportStats(); 437 | 438 | foreach (Report report in reports) 439 | { 440 | for (int i = 0; i < report.Reports.Count; i++) 441 | { 442 | switch (report.Reports[i].WarningType) 443 | { 444 | case WarningType.Info: 445 | reportStats.infoCount++; 446 | break; 447 | case WarningType.Warning: 448 | reportStats.warningCount++; 449 | break; 450 | case WarningType.Error: 451 | reportStats.errorCount++; 452 | break; 453 | default: 454 | break; 455 | } 456 | } 457 | } 458 | } 459 | 460 | private void OnValidatorInfoVisibilityChangedEvent(object info) 461 | { 462 | if(info is ValidatorInfo validatorInfo) 463 | { 464 | validatorInfo.isEnabled = !validatorInfo.isEnabled; 465 | } 466 | } 467 | 468 | private void OnValidatorInfoVisibilityAllEvent() 469 | { 470 | foreach (ValidatorInfo validatorInfo in validators) 471 | { 472 | validatorInfo.isEnabled = true; 473 | } 474 | } 475 | 476 | private void OnValidatorInfoVisibilityNoneEvent() 477 | { 478 | foreach (ValidatorInfo validatorInfo in validators) 479 | { 480 | validatorInfo.isEnabled = false; 481 | } 482 | } 483 | 484 | private static Type[] GetValidatorTypes() 485 | { 486 | return TypeCache.GetTypesDerivedFrom().ToArray(); 487 | } 488 | 489 | private static string GetWarningIconName(WarningType warningType) 490 | { 491 | return warningType switch 492 | { 493 | WarningType.Info => "d_console.infoicon.sml", 494 | WarningType.Warning => "d_console.warnicon.sml", 495 | WarningType.Error => "d_console.erroricon.sml", 496 | _ => "d_console.erroricon.inactive.sml", 497 | }; 498 | } 499 | } 500 | } -------------------------------------------------------------------------------- /Editor/ValidatorEditorWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8f89d88b32f5b7c40a089dbec6b2c406 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Validators.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 69e4deb3492cc174aa67baedeefdb790 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Validators/RequiredAttributeValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Reflection; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace Validator.Editor 8 | { 9 | public class RequiredAttributeAssetValidator : IValidator 10 | { 11 | public string MenuName => nameof(RequiredAttributeAssetValidator); 12 | 13 | private const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 14 | 15 | public Report Validate() 16 | { 17 | Report report = new Report(nameof(RequiredAttributeAssetValidator)); 18 | 19 | List objects = ValidatableAssetValidator.FindAssetsByType(); 20 | 21 | for (int i = 0; i < objects.Count; i++) 22 | { 23 | EditorUtility.DisplayProgressBar("RequiredAttributeAssetValidator", "RequiredAttribute...", (float)i / objects.Count); 24 | 25 | IEnumerable<(FieldInfo FieldInfo, RequiredAttribute Attribute)> fieldsWithRequiredAttribute = from fi in objects[i].GetType().GetFields(flags) 26 | let attr = fi.GetCustomAttributes(typeof(RequiredAttribute), true) 27 | where attr.Length == 1 28 | select (FieldInfo: fi, Attribute: attr.First() as RequiredAttribute); 29 | 30 | foreach ((FieldInfo FieldInfo, RequiredAttribute Attribute) field in fieldsWithRequiredAttribute) 31 | { 32 | object o = field.FieldInfo.GetValue(objects[i]); 33 | if (o == null || o.Equals(null)) 34 | { 35 | report.Log(objects[i], field.Attribute.WarningType, field.Attribute.Category, $"{field.FieldInfo.Name} is null", $"Assign {field.FieldInfo.FieldType}"); 36 | } 37 | } 38 | } 39 | EditorUtility.ClearProgressBar(); 40 | 41 | return report; 42 | } 43 | } 44 | 45 | public class RequiredAttributeSceneValidator : IValidator 46 | { 47 | public string MenuName => nameof(RequiredAttributeSceneValidator); 48 | 49 | private const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 50 | 51 | public Report Validate() 52 | { 53 | Report report = new Report(nameof(RequiredAttributeSceneValidator)); 54 | 55 | List objects = ValidatableSceneValidator.FindAllObjectsOfType(); 56 | 57 | for (int i = 0; i < objects.Count; i++) 58 | { 59 | EditorUtility.DisplayProgressBar("RequiredAttributeSceneValidator", "RequiredAttribute...", (float)i / objects.Count); 60 | IEnumerable<(FieldInfo FieldInfo, RequiredAttribute Attribute)> fieldsWithRequiredAttribute = from fi in objects[i].GetType().GetFields(flags) 61 | let attr = fi.GetCustomAttributes(typeof(RequiredAttribute), true) 62 | where attr.Length == 1 63 | select (FieldInfo: fi, Attribute: attr.First() as RequiredAttribute); 64 | 65 | foreach ((FieldInfo FieldInfo, RequiredAttribute Attribute) field in fieldsWithRequiredAttribute) 66 | { 67 | object o = field.FieldInfo.GetValue(objects[i]); 68 | if (o == null || o.Equals(null)) 69 | { 70 | report.Log(objects[i], field.Attribute.WarningType, field.Attribute.Category, $"{field.FieldInfo.Name} is null", $"Assign {field.FieldInfo.FieldType}"); 71 | } 72 | } 73 | } 74 | EditorUtility.ClearProgressBar(); 75 | 76 | return report; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Editor/Validators/RequiredAttributeValidator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 90d5c1b8964451644b0aa09257ab0bc3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Validators/ValidatableAssetValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Validator.Editor 6 | { 7 | public class ValidatableAssetValidator : IValidator 8 | { 9 | public string MenuName => nameof(ValidatableAssetValidator); 10 | 11 | public Report Validate() 12 | { 13 | Report report = new Report(nameof(ValidatableAssetValidator)); 14 | 15 | List objects = FindAssetsByType(); 16 | for (int i = 0; i < objects.Count; i++) 17 | { 18 | EditorUtility.DisplayProgressBar("AssetValidator", "Validate...", (float)i / objects.Count); 19 | if (objects[i] is IValidatable validatable) 20 | { 21 | validatable.Validate(report); 22 | } 23 | } 24 | EditorUtility.ClearProgressBar(); 25 | 26 | return report; 27 | } 28 | 29 | public static List FindAssetsByType() where T : Object 30 | { 31 | List assets = new List(); 32 | string[] guids = AssetDatabase.FindAssets(string.Format("t:{0}", typeof(T))); 33 | 34 | for (int i = 0; i < guids.Length; i++) 35 | { 36 | EditorUtility.DisplayProgressBar("AssetValidator", "FindAssetsByType...", (float)i / guids.Length); 37 | 38 | string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]); 39 | T asset = AssetDatabase.LoadAssetAtPath(assetPath); 40 | if (asset != null) 41 | { 42 | assets.Add(asset); 43 | } 44 | } 45 | EditorUtility.ClearProgressBar(); 46 | 47 | return assets; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Editor/Validators/ValidatableAssetValidator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 458ef52fac9354e4fb723a3a8645dd2d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Validators/ValidatableSceneValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | using UnityEngine.SceneManagement; 5 | 6 | namespace Validator.Editor 7 | { 8 | public class ValidatableSceneValidator : IValidator 9 | { 10 | public string MenuName => nameof(ValidatableSceneValidator); 11 | 12 | public Report Validate() 13 | { 14 | Report report = new Report(nameof(ValidatableSceneValidator)); 15 | 16 | List objects = FindAllObjectsOfType(); 17 | for (int i = 0; i < objects.Count; i++) 18 | { 19 | EditorUtility.DisplayProgressBar("SceneValidator", "Validate...", (float)i / objects.Count); 20 | 21 | objects[i].Validate(report); 22 | } 23 | EditorUtility.ClearProgressBar(); 24 | 25 | return report; 26 | } 27 | 28 | private static List GetAllRootGameObjects() 29 | { 30 | List gameObjects = new List(); 31 | 32 | for (int i = 0; i < SceneManager.sceneCount; i++) 33 | { 34 | EditorUtility.DisplayProgressBar("SceneValidator", "GetAllRootGameObjects...", (float)i / SceneManager.sceneCount); 35 | gameObjects.AddRange(SceneManager.GetSceneAt(i).GetRootGameObjects()); 36 | } 37 | EditorUtility.ClearProgressBar(); 38 | 39 | return gameObjects; 40 | } 41 | 42 | public static List FindAllObjectsOfType() 43 | { 44 | List objects = new List(); 45 | 46 | List gameObjects = GetAllRootGameObjects(); 47 | for (int i = 0; i < gameObjects.Count; i++) 48 | { 49 | EditorUtility.DisplayProgressBar("SceneValidator", "FindAllObjectsOfType...", (float)i / gameObjects.Count); 50 | objects.AddRange(gameObjects[i].GetComponentsInChildren(true)); 51 | } 52 | EditorUtility.ClearProgressBar(); 53 | 54 | return objects; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Editor/Validators/ValidatableSceneValidator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fced302525286954493f623662b36f33 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VertexColor.Validator.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Validator.Editor", 3 | "rootNamespace": "Validator.Editor", 4 | "references": [ 5 | "GUID:334c4fbff44e0f9439f29f9d8e0ce6e9" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/VertexColor.Validator.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20d798ece823f744489d29f7453b3315 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Max Kruf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e466f8c2ce68a6a4eaa6a680cb28895b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Validator 2 | 3 | ![ValidatorWindow](Documentation~/Images/ValidatorWindow_01.png) 4 | 5 | Unity project validator framework. 6 | 7 | ## Getting Started 8 | Add a custom validate check using the `IValidatable` interface: 9 | ```C# 10 | using Validator; 11 | 12 | public class MyBehaviour : MonoBehaviour, IValidatable 13 | { 14 | [SerializeField] private float startHealth = 10; // If someone was to put it to low <= 0, it would be invalid. 15 | 16 | #if UNITY_EDITOR 17 | public void Validate(Report report) 18 | { 19 | // Check if health is valid. 20 | if(startHealth <= 0) 21 | { 22 | // If not, log it. 23 | report.Log(this, WarningType.Warning, ReportCategories.Design, $"{nameof(startHealth)} is to low", $"Make value > 0"); 24 | } 25 | } 26 | #endif 27 | } 28 | ``` 29 | 30 | Add a validate check using `[Required]` attribute: 31 | ```C# 32 | [SerializeField, Required] private GameObject playerPrefab = null; // If someone forgets to assign it, it would be invalid. 33 | ``` 34 | 35 | Open Validator Window: 36 | > Window -> General -> Validator 37 | 38 | Run the validator: 39 | > Click the 'run/play' button and wait for the report to be generated. 40 | 41 | ## Install 42 | 43 | [Installing from a Git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html) 44 | 45 | ## LICENSE 46 | 47 | Overall package is licensed under [MIT](/LICENSE.md), unless otherwise noted in the [3rd party licenses](/THIRD%20PARTY%20NOTICES.md) file and/or source code. 48 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe23a067c33426a459b6554df6d0487e 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2752af9ac554eac43949290d597db9ab 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/IValidatable.cs: -------------------------------------------------------------------------------- 1 | namespace Validator 2 | { 3 | public interface IValidatable 4 | { 5 | #if UNITY_EDITOR 6 | public void Validate(Report report); 7 | #endif 8 | } 9 | } -------------------------------------------------------------------------------- /Runtime/IValidatable.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9745876369d865247b2bae9393491734 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Report.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace Validator 5 | { 6 | public enum WarningType 7 | { 8 | Info, 9 | Warning, 10 | Error, 11 | } 12 | 13 | public class Report 14 | { 15 | public class ReportMessage 16 | { 17 | public Object Target => target; 18 | public WarningType WarningType => warningType; 19 | public string Category => category; 20 | public string Message => message; 21 | public string Solution => solution; 22 | 23 | private readonly Object target; 24 | private readonly WarningType warningType; 25 | private readonly string category; 26 | private readonly string message; 27 | private readonly string solution; 28 | 29 | public ReportMessage(Object target, WarningType warningLevel, string category, string message, string solution) 30 | { 31 | this.target = target; 32 | this.warningType = warningLevel; 33 | this.category = category; 34 | this.message = message; 35 | this.solution = solution; 36 | } 37 | } 38 | 39 | public string Name => name; 40 | public IList Reports => reports.AsReadOnly(); 41 | 42 | private readonly string name; 43 | private readonly List reports = new List(); 44 | 45 | public Report(string name) 46 | { 47 | this.name = name; 48 | } 49 | 50 | public void Log(ReportMessage report) 51 | { 52 | reports.Add(report); 53 | } 54 | 55 | public void Log(Object target, WarningType warningType, string category, string message, string solution) 56 | { 57 | Log(new ReportMessage(target, warningType, category, message, solution)); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Runtime/Report.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d39518e0aab78db47adc586ce678d8a9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ReportCategories.cs: -------------------------------------------------------------------------------- 1 | namespace Validator 2 | { 3 | public static partial class ReportCategories 4 | { 5 | public const string Art = "Art"; 6 | public const string Design = "Design"; 7 | public const string Code = "Code"; 8 | } 9 | } -------------------------------------------------------------------------------- /Runtime/ReportCategories.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2016a6a28d54304ea9cd64801c4f0b9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Validators.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8cd25e23ad137e24ea0f469556831712 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Validators/Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 25705f09a21a10049aed213ea301db84 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Validators/Attributes/RequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Validator 4 | { 5 | [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] 6 | public class RequiredAttribute : Attribute 7 | { 8 | public WarningType WarningType { get; private set; } = WarningType.Error; 9 | public string Category { get; private set; } = ReportCategories.Design; 10 | 11 | public RequiredAttribute() { } 12 | 13 | public RequiredAttribute(WarningType warningType = WarningType.Error, string category = ReportCategories.Design) 14 | { 15 | WarningType = warningType; 16 | Category = category; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Runtime/Validators/Attributes/RequiredAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4dbbb107f0e849140b1132ac5dcca90f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/VertexColor.Validator.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VertexColor.Validator", 3 | "rootNamespace": "Validator", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Runtime/VertexColor.Validator.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 334c4fbff44e0f9439f29f9d8e0ce6e9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /THIRD PARTY NOTICES.md: -------------------------------------------------------------------------------- 1 | This package borrows code from various different sources, including: 2 | 3 | # []() 4 | 5 | ### Relevant Files 6 | - []() 7 | 8 | ### Credits 9 | - Author: []() 10 | - Source: []() 11 | - License: []() 12 | -------------------------------------------------------------------------------- /THIRD PARTY NOTICES.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e3e716ef6981e984eb7f8a0196a2dc92 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.vertexcolor.validator", 3 | "displayName": "Validator", 4 | "version": "0.1.4", 5 | "unity": "2019.3", 6 | "description": "Unity project validator framework.", 7 | "category": "Tool", 8 | "type": "tool", 9 | "license": "MIT", 10 | "author": { 11 | "name": "Max Kruf", 12 | "email": "info@maxartz15.com", 13 | "url": "https://www.maxartz15.com" 14 | }, 15 | "keywords": [ 16 | "Validator" 17 | ] 18 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 84601f755fc0e304f85a3ebf67e1601b 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------