1
0

Program.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Threading.Tasks;
  6. using Content.IntegrationTests;
  7. using Robust.Shared.Prototypes;
  8. using Robust.Shared.Reflection;
  9. using Robust.Shared.Serialization.Markdown.Validation;
  10. using Robust.Shared.Timing;
  11. using Robust.Shared.Utility;
  12. using Robust.UnitTesting;
  13. namespace Content.YAMLLinter
  14. {
  15. internal static class Program
  16. {
  17. private static async Task<int> Main(string[] _)
  18. {
  19. PoolManager.Startup();
  20. var stopwatch = new Stopwatch();
  21. stopwatch.Start();
  22. var (errors, fieldErrors) = await RunValidation();
  23. var count = errors.Count + fieldErrors.Count;
  24. if (count == 0)
  25. {
  26. Console.WriteLine($"No errors found in {(int) stopwatch.Elapsed.TotalMilliseconds} ms.");
  27. PoolManager.Shutdown();
  28. return 0;
  29. }
  30. foreach (var (file, errorHashset) in errors)
  31. {
  32. foreach (var errorNode in errorHashset)
  33. {
  34. Console.WriteLine($"::error file={file},line={errorNode.Node.Start.Line},col={errorNode.Node.Start.Column}::{file}({errorNode.Node.Start.Line},{errorNode.Node.Start.Column}) {errorNode.ErrorReason}");
  35. }
  36. }
  37. foreach (var error in fieldErrors)
  38. {
  39. Console.WriteLine(error);
  40. }
  41. Console.WriteLine($"{count} errors found in {(int) stopwatch.Elapsed.TotalMilliseconds} ms.");
  42. PoolManager.Shutdown();
  43. return -1;
  44. }
  45. private static async Task<(Dictionary<string, HashSet<ErrorNode>> YamlErrors, List<string> FieldErrors)>
  46. ValidateClient()
  47. {
  48. await using var pair = await PoolManager.GetServerClient();
  49. var client = pair.Client;
  50. var result = await ValidateInstance(client);
  51. await pair.CleanReturnAsync();
  52. return result;
  53. }
  54. private static async Task<(Dictionary<string, HashSet<ErrorNode>> YamlErrors, List<string> FieldErrors)>
  55. ValidateServer()
  56. {
  57. await using var pair = await PoolManager.GetServerClient();
  58. var server = pair.Server;
  59. var result = await ValidateInstance(server);
  60. await pair.CleanReturnAsync();
  61. return result;
  62. }
  63. private static async Task<(Dictionary<string, HashSet<ErrorNode>>, List<string>)> ValidateInstance(
  64. RobustIntegrationTest.IntegrationInstance instance)
  65. {
  66. var protoMan = instance.ResolveDependency<IPrototypeManager>();
  67. Dictionary<string, HashSet<ErrorNode>> yamlErrors = default!;
  68. List<string> fieldErrors = default!;
  69. await instance.WaitPost(() =>
  70. {
  71. var engineErrors = protoMan.ValidateDirectory(new ResPath("/EnginePrototypes"), out var engPrototypes);
  72. yamlErrors = protoMan.ValidateDirectory(new ResPath("/Prototypes"), out var prototypes);
  73. // Merge engine & content prototypes
  74. foreach (var (kind, instances) in engPrototypes)
  75. {
  76. if (prototypes.TryGetValue(kind, out var existing))
  77. existing.UnionWith(instances);
  78. else
  79. prototypes[kind] = instances;
  80. }
  81. foreach (var (kind, set) in engineErrors)
  82. {
  83. if (yamlErrors.TryGetValue(kind, out var existing))
  84. existing.UnionWith(set);
  85. else
  86. yamlErrors[kind] = set;
  87. }
  88. fieldErrors = protoMan.ValidateStaticFields(prototypes);
  89. });
  90. return (yamlErrors, fieldErrors);
  91. }
  92. public static async Task<(Dictionary<string, HashSet<ErrorNode>> YamlErrors, List<string> FieldErrors)>
  93. RunValidation()
  94. {
  95. var (clientAssemblies, serverAssemblies) = await GetClientServerAssemblies();
  96. var serverTypes = serverAssemblies.SelectMany(n => n.GetTypes()).Select(t => t.Name).ToHashSet();
  97. var clientTypes = clientAssemblies.SelectMany(n => n.GetTypes()).Select(t => t.Name).ToHashSet();
  98. var yamlErrors = new Dictionary<string, HashSet<ErrorNode>>();
  99. var serverErrors = await ValidateServer();
  100. var clientErrors = await ValidateClient();
  101. foreach (var (key, val) in serverErrors.YamlErrors)
  102. {
  103. // Include all server errors marked as always relevant
  104. var newErrors = val.Where(n => n.AlwaysRelevant).ToHashSet();
  105. // We include sometimes-relevant errors if they exist both for the client & server
  106. if (clientErrors.YamlErrors.TryGetValue(key, out var clientVal))
  107. newErrors.UnionWith(val.Intersect(clientVal));
  108. // Include any errors that relate to server-only types
  109. foreach (var errorNode in val)
  110. {
  111. if (errorNode is FieldNotFoundErrorNode fieldNotFoundNode && !clientTypes.Contains(fieldNotFoundNode.FieldType.Name))
  112. {
  113. newErrors.Add(errorNode);
  114. }
  115. }
  116. if (newErrors.Count != 0)
  117. yamlErrors[key] = newErrors;
  118. }
  119. // Next add any always-relevant client errors.
  120. foreach (var (key, val) in clientErrors.YamlErrors)
  121. {
  122. var newErrors = val.Where(n => n.AlwaysRelevant).ToHashSet();
  123. if (newErrors.Count == 0)
  124. continue;
  125. if (yamlErrors.TryGetValue(key, out var errors))
  126. errors.UnionWith(val.Where(n => n.AlwaysRelevant));
  127. else
  128. yamlErrors[key] = newErrors;
  129. // Include any errors that relate to client-only types
  130. foreach (var errorNode in val)
  131. {
  132. if (errorNode is FieldNotFoundErrorNode fieldNotFoundNode && !serverTypes.Contains(fieldNotFoundNode.FieldType.Name))
  133. {
  134. newErrors.Add(errorNode);
  135. }
  136. }
  137. }
  138. // Finally, combine the prototype ID field errors.
  139. var fieldErrors = serverErrors.FieldErrors
  140. .Concat(clientErrors.FieldErrors)
  141. .Distinct()
  142. .ToList();
  143. return (yamlErrors, fieldErrors);
  144. }
  145. private static async Task<(Assembly[] clientAssemblies, Assembly[] serverAssemblies)>
  146. GetClientServerAssemblies()
  147. {
  148. await using var pair = await PoolManager.GetServerClient();
  149. var result = (GetAssemblies(pair.Client), GetAssemblies(pair.Server));
  150. await pair.CleanReturnAsync();
  151. return result;
  152. Assembly[] GetAssemblies(RobustIntegrationTest.IntegrationInstance instance)
  153. {
  154. var refl = instance.ResolveDependency<IReflectionManager>();
  155. return refl.Assemblies.ToArray();
  156. }
  157. }
  158. }
  159. }