com.wallstop-studios.dxmessaging 2.2.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +106 -67
  2. package/CHANGELOG.md.meta +7 -7
  3. package/Editor/Analyzers/BaseCallIlInspector.cs +277 -0
  4. package/Editor/Analyzers/BaseCallIlInspector.cs.meta +11 -0
  5. package/Editor/Analyzers/BaseCallLogMessageParser.cs +295 -0
  6. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +11 -0
  7. package/Editor/Analyzers/BaseCallReportAggregator.cs +308 -0
  8. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +11 -0
  9. package/Editor/Analyzers/BaseCallTypeScanner.cs +110 -0
  10. package/Editor/Analyzers/BaseCallTypeScanner.cs.meta +11 -0
  11. package/Editor/Analyzers/BaseCallTypeScannerCore.cs +562 -0
  12. package/Editor/Analyzers/BaseCallTypeScannerCore.cs.meta +11 -0
  13. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +1122 -0
  14. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +11 -0
  15. package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +44 -44
  16. package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +44 -44
  17. package/Editor/Analyzers/System.Collections.Immutable.dll.meta +44 -44
  18. package/Editor/Analyzers/System.Reflection.Metadata.dll.meta +44 -44
  19. package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +44 -44
  20. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  21. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +33 -0
  22. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  23. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +72 -72
  24. package/Editor/Analyzers.meta +8 -8
  25. package/Editor/AssemblyInfo.cs.meta +3 -3
  26. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +81 -0
  27. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +11 -0
  28. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +420 -0
  29. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +11 -0
  30. package/Editor/CustomEditors/MessagingComponentEditor.cs +1 -1
  31. package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +2 -2
  32. package/Editor/CustomEditors.meta +2 -2
  33. package/Editor/DxMessagingEditorInitializer.cs.meta +2 -2
  34. package/Editor/DxMessagingMenu.cs.meta +11 -11
  35. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -11
  36. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +190 -0
  37. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +11 -0
  38. package/Editor/Settings/DxMessagingSettings.cs +189 -0
  39. package/Editor/Settings/DxMessagingSettings.cs.meta +2 -2
  40. package/Editor/Settings/DxMessagingSettingsProvider.cs +50 -33
  41. package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +2 -2
  42. package/Editor/Settings.meta +2 -2
  43. package/Editor/SetupCscRsp.cs +209 -8
  44. package/Editor/SetupCscRsp.cs.meta +2 -2
  45. package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
  46. package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +3 -3
  47. package/Editor/Testing.meta +3 -3
  48. package/Editor/WallstopStudios.DxMessaging.Editor.asmdef +14 -14
  49. package/Editor/WallstopStudios.DxMessaging.Editor.asmdef.meta +7 -7
  50. package/Editor.meta +8 -8
  51. package/LICENSE.md +9 -9
  52. package/LICENSE.md.meta +7 -7
  53. package/README.md +941 -900
  54. package/README.md.meta +7 -7
  55. package/Runtime/AssemblyInfo.cs +4 -0
  56. package/Runtime/AssemblyInfo.cs.meta +2 -2
  57. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +2 -2
  58. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +2 -2
  59. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs +26 -0
  60. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +11 -0
  61. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +2 -2
  62. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +2 -2
  63. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +2 -2
  64. package/Runtime/Core/Attributes.meta +2 -2
  65. package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs +195 -0
  66. package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs.meta +11 -0
  67. package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs +179 -0
  68. package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs.meta +11 -0
  69. package/Runtime/Core/Configuration.meta +9 -0
  70. package/Runtime/Core/DataStructure/CyclicBuffer.cs +2 -2
  71. package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +2 -2
  72. package/Runtime/Core/DataStructure.meta +2 -2
  73. package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +2 -2
  74. package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +2 -2
  75. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +2 -2
  76. package/Runtime/Core/Diagnostics.meta +2 -2
  77. package/Runtime/Core/DxMessagingStaticState.cs +19 -0
  78. package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -11
  79. package/Runtime/Core/Extensions/EnumExtensions.cs.meta +2 -2
  80. package/Runtime/Core/Extensions/IListExtensions.cs.meta +2 -2
  81. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -12
  82. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +11 -11
  83. package/Runtime/Core/Extensions.meta +8 -8
  84. package/Runtime/Core/Helper/MessageCache.cs +32 -0
  85. package/Runtime/Core/Helper/MessageCache.cs.meta +2 -2
  86. package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +2 -2
  87. package/Runtime/Core/Helper.meta +2 -2
  88. package/Runtime/Core/IMessage.cs +3 -3
  89. package/Runtime/Core/IMessage.cs.meta +11 -11
  90. package/Runtime/Core/InstanceId.cs.meta +11 -11
  91. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +51 -0
  92. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta +11 -0
  93. package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs +38 -0
  94. package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs.meta +11 -0
  95. package/Runtime/Core/Internal/TypedSlotIndex.cs +81 -0
  96. package/Runtime/Core/Internal/TypedSlotIndex.cs.meta +11 -0
  97. package/Runtime/Core/Internal/TypedSlots.cs +613 -0
  98. package/Runtime/Core/Internal/TypedSlots.cs.meta +11 -0
  99. package/Runtime/Core/Internal.meta +9 -0
  100. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -11
  101. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -11
  102. package/Runtime/Core/MessageBus/IMessageBus.cs +177 -3
  103. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +11 -11
  104. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -11
  105. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -11
  106. package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs +16 -0
  107. package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs.meta +11 -0
  108. package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs +40 -0
  109. package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs.meta +11 -0
  110. package/Runtime/Core/MessageBus/Internal/BusSlots.cs +718 -0
  111. package/Runtime/Core/MessageBus/Internal/BusSlots.cs.meta +11 -0
  112. package/Runtime/Core/MessageBus/Internal/DispatchKind.cs +38 -0
  113. package/Runtime/Core/MessageBus/Internal/DispatchKind.cs.meta +11 -0
  114. package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs +20 -0
  115. package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs.meta +11 -0
  116. package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs +28 -0
  117. package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs.meta +11 -0
  118. package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs +48 -0
  119. package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs.meta +11 -0
  120. package/Runtime/Core/MessageBus/Internal/ISweepable.cs +15 -0
  121. package/Runtime/Core/MessageBus/Internal/ISweepable.cs.meta +11 -0
  122. package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs +222 -0
  123. package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs.meta +11 -0
  124. package/Runtime/Core/MessageBus/Internal/SlotKey.cs +192 -0
  125. package/Runtime/Core/MessageBus/Internal/SlotKey.cs.meta +11 -0
  126. package/Runtime/Core/MessageBus/Internal.meta +9 -0
  127. package/Runtime/Core/MessageBus/MessageBus.cs +2651 -500
  128. package/Runtime/Core/MessageBus/MessageBus.cs.meta +11 -11
  129. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -11
  130. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -11
  131. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +11 -11
  132. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +11 -11
  133. package/Runtime/Core/MessageBus.meta +8 -8
  134. package/Runtime/Core/MessageHandler.cs +2019 -542
  135. package/Runtime/Core/MessageHandler.cs.meta +11 -11
  136. package/Runtime/Core/MessageRegistrationHandle.cs.meta +11 -11
  137. package/Runtime/Core/MessageRegistrationToken.cs +7 -0
  138. package/Runtime/Core/MessageRegistrationToken.cs.meta +11 -11
  139. package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +2 -2
  140. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +11 -11
  141. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +11 -11
  142. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +11 -11
  143. package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +2 -2
  144. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +11 -11
  145. package/Runtime/Core/Messages/StringMessage.cs.meta +2 -2
  146. package/Runtime/Core/Messages.meta +8 -8
  147. package/Runtime/Core/MessagingDebug.cs.meta +11 -11
  148. package/Runtime/Core/Pooling/CollectionPool.cs +266 -0
  149. package/Runtime/Core/Pooling/CollectionPool.cs.meta +11 -0
  150. package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs +30 -0
  151. package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs.meta +11 -0
  152. package/Runtime/Core/Pooling/DxPools.cs +157 -0
  153. package/Runtime/Core/Pooling/DxPools.cs.meta +11 -0
  154. package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs +106 -0
  155. package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs.meta +11 -0
  156. package/Runtime/Core/Pooling/IDxMessagingClock.cs +18 -0
  157. package/Runtime/Core/Pooling/IDxMessagingClock.cs.meta +11 -0
  158. package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs +55 -0
  159. package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs.meta +11 -0
  160. package/Runtime/Core/Pooling/StopwatchClock.cs +27 -0
  161. package/Runtime/Core/Pooling/StopwatchClock.cs.meta +11 -0
  162. package/Runtime/Core/Pooling/UnityRealtimeClock.cs +31 -0
  163. package/Runtime/Core/Pooling/UnityRealtimeClock.cs.meta +11 -0
  164. package/Runtime/Core/Pooling.meta +9 -0
  165. package/Runtime/Core.meta +8 -8
  166. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +12 -12
  167. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -11
  168. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +12 -12
  169. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +2 -2
  170. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +73 -0
  171. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +11 -11
  172. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef +20 -20
  173. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef.meta +7 -7
  174. package/Runtime/Unity/Integrations/Reflex.meta +8 -8
  175. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +2 -2
  176. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +109 -1
  177. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +11 -11
  178. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef +30 -30
  179. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef.meta +7 -7
  180. package/Runtime/Unity/Integrations/VContainer.meta +8 -8
  181. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +2 -2
  182. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef +30 -30
  183. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef.meta +7 -7
  184. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +79 -1
  185. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +11 -11
  186. package/Runtime/Unity/Integrations/Zenject.meta +8 -8
  187. package/Runtime/Unity/Integrations.meta +8 -8
  188. package/Runtime/Unity/MessageAwareComponent.cs +29 -0
  189. package/Runtime/Unity/MessageAwareComponent.cs.meta +11 -11
  190. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +12 -12
  191. package/Runtime/Unity/MessagingComponent.cs.meta +11 -11
  192. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +12 -12
  193. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +12 -12
  194. package/Runtime/Unity.meta +8 -8
  195. package/Runtime/WallstopStudios.DxMessaging.asmdef +14 -14
  196. package/Runtime/WallstopStudios.DxMessaging.asmdef.meta +7 -7
  197. package/Runtime.meta +8 -8
  198. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab +98 -98
  199. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab.meta +7 -7
  200. package/Samples~/DI/Prefabs.meta +8 -8
  201. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset +14 -14
  202. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset.meta +8 -8
  203. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset +14 -14
  204. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset.meta +8 -8
  205. package/Samples~/DI/Providers.meta +8 -8
  206. package/Samples~/DI/README.md +51 -51
  207. package/Samples~/DI/README.md.meta +7 -7
  208. package/Samples~/DI/Reflex/SampleInstaller.cs +7 -0
  209. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +11 -11
  210. package/Samples~/DI/Reflex.meta +8 -8
  211. package/Samples~/DI/VContainer/SampleLifetimeScope.cs +6 -1
  212. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +11 -11
  213. package/Samples~/DI/VContainer.meta +8 -8
  214. package/Samples~/DI/Zenject/SampleInstaller.cs +8 -0
  215. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +11 -11
  216. package/Samples~/DI/Zenject.meta +8 -8
  217. package/Samples~/DI.meta +8 -8
  218. package/Samples~/Mini Combat/Boot.cs.meta +11 -11
  219. package/Samples~/Mini Combat/Enemy.cs.meta +11 -11
  220. package/Samples~/Mini Combat/Messages.cs.meta +11 -11
  221. package/Samples~/Mini Combat/Player.cs.meta +11 -11
  222. package/Samples~/Mini Combat/README.md +324 -323
  223. package/Samples~/Mini Combat/README.md.meta +7 -7
  224. package/Samples~/Mini Combat/UIOverlay.cs.meta +11 -11
  225. package/Samples~/Mini Combat/Walkthrough.md +430 -430
  226. package/Samples~/Mini Combat/Walkthrough.md.meta +7 -7
  227. package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef +13 -13
  228. package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef.meta +7 -7
  229. package/Samples~/Mini Combat.meta +8 -8
  230. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +11 -11
  231. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +11 -11
  232. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +11 -11
  233. package/Samples~/UI Buttons + Inspector/README.md +210 -209
  234. package/Samples~/UI Buttons + Inspector/README.md.meta +7 -7
  235. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +11 -11
  236. package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef +13 -13
  237. package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef.meta +7 -7
  238. package/Samples~/UI Buttons + Inspector.meta +8 -8
  239. package/SourceGenerators/Directory.Build.props.meta +7 -7
  240. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +11 -11
  241. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +2 -2
  242. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj.meta +7 -7
  243. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.meta +8 -8
  244. package/SourceGenerators.meta +8 -8
  245. package/Third Party Notices.md +3 -3
  246. package/Third Party Notices.md.meta +7 -7
  247. package/package.json +115 -92
  248. package/package.json.meta +7 -7
@@ -0,0 +1,295 @@
1
+ // The Unity Editor assembly that hosts this file does not enable nullable annotations; the
2
+ // dotnet-test project that compiles a linked copy DOES (`<Nullable>enable</Nullable>`). Pin the
3
+ // nullable state per-file so behavior is identical in both compilation contexts.
4
+ #nullable disable
5
+ namespace DxMessaging.Editor.Analyzers
6
+ {
7
+ using System;
8
+ using System.Collections.Generic;
9
+ using System.Globalization;
10
+ using System.Text.RegularExpressions;
11
+
12
+ /// <summary>
13
+ /// Single parsed diagnostic line emitted by the <c>MessageAwareComponentBaseCallAnalyzer</c>.
14
+ /// </summary>
15
+ /// <remarks>
16
+ /// Empty <see cref="MethodName"/> is the documented sentinel for DXMSG008 (which carries no
17
+ /// method name in its message format). Empty <see cref="FilePath"/> / zero <see cref="Line"/>
18
+ /// indicate the bare (Unity-prefix-less) form of the message.
19
+ /// </remarks>
20
+ public readonly struct ParsedEntry
21
+ {
22
+ public string DiagnosticId { get; }
23
+
24
+ public string TypeFullName { get; }
25
+
26
+ public string MethodName { get; }
27
+
28
+ public string FilePath { get; }
29
+
30
+ public int Line { get; }
31
+
32
+ public ParsedEntry(
33
+ string diagnosticId,
34
+ string typeFullName,
35
+ string methodName,
36
+ string filePath,
37
+ int line
38
+ )
39
+ {
40
+ DiagnosticId = diagnosticId;
41
+ TypeFullName = typeFullName;
42
+ MethodName = methodName;
43
+ FilePath = filePath;
44
+ Line = line;
45
+ }
46
+ }
47
+
48
+ /// <summary>
49
+ /// Per-type aggregate produced by <see cref="BaseCallLogMessageParser.Aggregate"/>.
50
+ /// </summary>
51
+ /// <remarks>
52
+ /// <see cref="MissingBaseFor"/> is deduplicated and stored in deterministic ordinal-sorted order
53
+ /// via <see cref="SortedSet{T}"/>. <see cref="DiagnosticIds"/> uses ordinal comparison.
54
+ /// <see cref="FilePath"/> / <see cref="Line"/> hold the FIRST seen non-empty values so the
55
+ /// inspector overlay's "Open Script" jump remains stable across repeated parses.
56
+ /// </remarks>
57
+ public sealed class ParsedTypeReport
58
+ {
59
+ public string TypeFullName { get; set; }
60
+
61
+ public SortedSet<string> MissingBaseFor { get; } = new(StringComparer.Ordinal);
62
+
63
+ public HashSet<string> DiagnosticIds { get; } = new(StringComparer.Ordinal);
64
+
65
+ public string FilePath { get; set; }
66
+
67
+ public int Line { get; set; }
68
+ }
69
+
70
+ /// <summary>
71
+ /// Pure parser for the DXMSG006/DXMSG007/DXMSG008/DXMSG009/DXMSG010 console-log lines emitted
72
+ /// by the <c>MessageAwareComponentBaseCallAnalyzer</c>.
73
+ /// </summary>
74
+ /// <remarks>
75
+ /// <para>
76
+ /// This class lives in the Editor assembly so the harvester can call it directly without
77
+ /// referencing the Roslyn-analyzer DLL (Unity excludes <c>RoslynAnalyzer</c>-labelled
78
+ /// assemblies from asmdef compile-time references).
79
+ /// </para>
80
+ /// <para>
81
+ /// The analyzer-tests project compiles its own copy of this file via a
82
+ /// <c>&lt;Compile Include="...\BaseCallLogMessageParser.cs" Link="..." /&gt;</c> so the
83
+ /// existing dotnet-test coverage continues to run.
84
+ /// </para>
85
+ /// <para>
86
+ /// The regexes are pinned to the analyzer's verbatim message-format strings. Whenever those
87
+ /// formats change, both this parser AND <c>BaseCallLogMessageParserTests</c> must be updated
88
+ /// in lockstep.
89
+ /// </para>
90
+ /// </remarks>
91
+ public static class BaseCallLogMessageParser
92
+ {
93
+ // Roslyn / Unity-style location prefix: path(line,col): warning DXMSG006:
94
+ // We don't anchor to the diagnostic id here beyond the leading "DXMSG"; that lets the
95
+ // same prefix regex serve all five diagnostics (DXMSG006/007/008/009/010). The trailing
96
+ // `: ` is consumed so the diagnostic-specific regexes only see the message body.
97
+ private const RegexOptions SharedOptions =
98
+ RegexOptions.Compiled | RegexOptions.CultureInvariant;
99
+
100
+ private static readonly Regex PrefixRegex = new(
101
+ @"^(?<path>[^()\r\n]+?)\((?<line>\d+),(?<col>\d+)\)\s*:\s*(?:warning|error|info|hidden|message)\s+DXMSG0(?:0[6789]|10)\s*:\s*",
102
+ SharedOptions
103
+ );
104
+
105
+ // DXMSG006 format:
106
+ // '{type}' overrides MessageAwareComponent.{method} but does not call base.{method}();
107
+ // the messaging system may not function correctly on this component.
108
+ // The type name is captured from the first single-quoted token; the method from the first
109
+ // `MessageAwareComponent.{method}` occurrence (the format string repeats `{method}`).
110
+ // Body regexes anchor to the start of the body (^) so a Debug.Log payload that *contains*
111
+ // the analyzer's wording mid-string is not surfaced as a real DXMSG006/007/008. The prefix
112
+ // (when present) is stripped before this match, so ^ here is the start of the message body.
113
+ private static readonly Regex Dxmsg006Regex = new(
114
+ @"^'(?<type>[^']+)'\s+overrides\s+MessageAwareComponent\.(?<method>[A-Za-z_][A-Za-z0-9_]*)\s+but\s+does\s+not\s+call\s+base\.[A-Za-z_][A-Za-z0-9_]*\(\)\s*;\s*the\s+messaging\s+system\s+may\s+not\s+function\s+correctly\s+on\s+this\s+component\.",
115
+ SharedOptions
116
+ );
117
+
118
+ // DXMSG007 format:
119
+ // '{type}' hides MessageAwareComponent.{method} with 'new'; replace with 'override' and
120
+ // call base.{method}() so the messaging system continues to function.
121
+ private static readonly Regex Dxmsg007Regex = new(
122
+ @"^'(?<type>[^']+)'\s+hides\s+MessageAwareComponent\.(?<method>[A-Za-z_][A-Za-z0-9_]*)\s+with\s+'new'\s*;\s*replace\s+with\s+'override'\s+and\s+call\s+base\.[A-Za-z_][A-Za-z0-9_]*\(\)\s+so\s+the\s+messaging\s+system\s+continues\s+to\s+function\.",
123
+ SharedOptions
124
+ );
125
+
126
+ // DXMSG008 format:
127
+ // '{type}' is excluded from the DxMessaging base-call check ({source}).
128
+ // No method name in the message; MethodName is returned as the empty string.
129
+ private static readonly Regex Dxmsg008Regex = new(
130
+ @"^'(?<type>[^']+)'\s+is\s+excluded\s+from\s+the\s+DxMessaging\s+base-call\s+check\s+\([^)]*\)\.",
131
+ SharedOptions
132
+ );
133
+
134
+ // DXMSG009 format:
135
+ // '{type}' declares {method} without 'override' or 'new'; this implicitly hides
136
+ // MessageAwareComponent.{method} (CS0114) and the messaging system will not function. ...
137
+ // We anchor on the head of the message and stop after the modifier-tokens phrase so future
138
+ // wording tweaks to the trailing remediation text don't break the parser.
139
+ private static readonly Regex Dxmsg009Regex = new(
140
+ @"^'(?<type>[^']+)'\s+declares\s+(?<method>[A-Za-z_][A-Za-z0-9_]*)\s+without\s+'override'\s+or\s+'new'",
141
+ SharedOptions
142
+ );
143
+
144
+ // DXMSG010 format:
145
+ // '{type}' calls base.{method}() but the inherited override on '{broken}' does not
146
+ // chain to MessageAwareComponent.{method}; the messaging system will not function
147
+ // correctly on this component.
148
+ // We capture {type} (the class the user is editing), {method}, and the broken-ancestor
149
+ // FQN so the inspector overlay can mention "broken chain via {broken}" if desired.
150
+ private static readonly Regex Dxmsg010Regex = new(
151
+ @"^'(?<type>[^']+)'\s+calls\s+base\.(?<method>[A-Za-z_][A-Za-z0-9_]*)\(\)\s+but\s+the\s+inherited\s+override\s+on\s+'(?<broken>[^']+)'",
152
+ SharedOptions
153
+ );
154
+
155
+ /// <summary>
156
+ /// Parses one console log line. Returns <c>null</c> if the line is not a recognised
157
+ /// DXMSG006/DXMSG007/DXMSG008/DXMSG009/DXMSG010 message.
158
+ /// </summary>
159
+ /// <remarks>
160
+ /// Tolerates both the Roslyn-prefixed form (<c>Path(L,C): warning DXMSG006: ...</c>) and the
161
+ /// bare form (just the message body). The prefix's path/line are captured; the bare form
162
+ /// returns empty path / zero line.
163
+ /// </remarks>
164
+ public static ParsedEntry? ParseLine(string logLine)
165
+ {
166
+ if (string.IsNullOrWhiteSpace(logLine))
167
+ {
168
+ return null;
169
+ }
170
+
171
+ string filePath = string.Empty;
172
+ int line = 0;
173
+ string body = logLine;
174
+
175
+ Match prefixMatch = PrefixRegex.Match(logLine);
176
+ if (prefixMatch.Success)
177
+ {
178
+ filePath = prefixMatch.Groups["path"].Value;
179
+ if (
180
+ !int.TryParse(
181
+ prefixMatch.Groups["line"].Value,
182
+ NumberStyles.Integer,
183
+ CultureInfo.InvariantCulture,
184
+ out line
185
+ )
186
+ )
187
+ {
188
+ line = 0;
189
+ }
190
+ body = logLine.Substring(prefixMatch.Length);
191
+ }
192
+
193
+ Match dxmsg006 = Dxmsg006Regex.Match(body);
194
+ if (dxmsg006.Success)
195
+ {
196
+ string type = dxmsg006.Groups["type"].Value;
197
+ string method = dxmsg006.Groups["method"].Value;
198
+ if (!string.IsNullOrWhiteSpace(type) && !string.IsNullOrWhiteSpace(method))
199
+ {
200
+ return new ParsedEntry("DXMSG006", type, method, filePath, line);
201
+ }
202
+ }
203
+
204
+ Match dxmsg007 = Dxmsg007Regex.Match(body);
205
+ if (dxmsg007.Success)
206
+ {
207
+ string type = dxmsg007.Groups["type"].Value;
208
+ string method = dxmsg007.Groups["method"].Value;
209
+ if (!string.IsNullOrWhiteSpace(type) && !string.IsNullOrWhiteSpace(method))
210
+ {
211
+ return new ParsedEntry("DXMSG007", type, method, filePath, line);
212
+ }
213
+ }
214
+
215
+ Match dxmsg008 = Dxmsg008Regex.Match(body);
216
+ if (dxmsg008.Success)
217
+ {
218
+ string type = dxmsg008.Groups["type"].Value;
219
+ if (!string.IsNullOrWhiteSpace(type))
220
+ {
221
+ return new ParsedEntry("DXMSG008", type, string.Empty, filePath, line);
222
+ }
223
+ }
224
+
225
+ Match dxmsg009 = Dxmsg009Regex.Match(body);
226
+ if (dxmsg009.Success)
227
+ {
228
+ string type = dxmsg009.Groups["type"].Value;
229
+ string method = dxmsg009.Groups["method"].Value;
230
+ if (!string.IsNullOrWhiteSpace(type) && !string.IsNullOrWhiteSpace(method))
231
+ {
232
+ return new ParsedEntry("DXMSG009", type, method, filePath, line);
233
+ }
234
+ }
235
+
236
+ Match dxmsg010 = Dxmsg010Regex.Match(body);
237
+ if (dxmsg010.Success)
238
+ {
239
+ string type = dxmsg010.Groups["type"].Value;
240
+ string method = dxmsg010.Groups["method"].Value;
241
+ if (!string.IsNullOrWhiteSpace(type) && !string.IsNullOrWhiteSpace(method))
242
+ {
243
+ return new ParsedEntry("DXMSG010", type, method, filePath, line);
244
+ }
245
+ }
246
+
247
+ return null;
248
+ }
249
+
250
+ /// <summary>
251
+ /// Aggregates many log lines into a per-type report keyed by FQN. Deduplicates methods and
252
+ /// diagnostic ids ordinally; method names are stored in deterministic ordinal-sorted order.
253
+ /// Preserves first-occurrence for <see cref="ParsedTypeReport.FilePath"/> /
254
+ /// <see cref="ParsedTypeReport.Line"/>.
255
+ /// </summary>
256
+ public static Dictionary<string, ParsedTypeReport> Aggregate(IEnumerable<string> logLines)
257
+ {
258
+ Dictionary<string, ParsedTypeReport> result = new(StringComparer.Ordinal);
259
+ if (logLines == null)
260
+ {
261
+ return result;
262
+ }
263
+
264
+ foreach (string logLine in logLines)
265
+ {
266
+ ParsedEntry? parsed = ParseLine(logLine);
267
+ if (parsed is not ParsedEntry entry)
268
+ {
269
+ continue;
270
+ }
271
+
272
+ if (!result.TryGetValue(entry.TypeFullName, out ParsedTypeReport report))
273
+ {
274
+ report = new ParsedTypeReport { TypeFullName = entry.TypeFullName };
275
+ result[entry.TypeFullName] = report;
276
+ }
277
+
278
+ report.DiagnosticIds.Add(entry.DiagnosticId);
279
+
280
+ if (!string.IsNullOrEmpty(entry.MethodName))
281
+ {
282
+ report.MissingBaseFor.Add(entry.MethodName);
283
+ }
284
+
285
+ if (string.IsNullOrEmpty(report.FilePath) && !string.IsNullOrEmpty(entry.FilePath))
286
+ {
287
+ report.FilePath = entry.FilePath;
288
+ report.Line = entry.Line;
289
+ }
290
+ }
291
+
292
+ return result;
293
+ }
294
+ }
295
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: a17c13ebe9604cee9bfa08d2517a3fad
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,308 @@
1
+ // The Unity Editor assembly that hosts this file does not enable nullable annotations; the
2
+ // dotnet-test project that compiles a linked copy DOES (`<Nullable>enable</Nullable>`). Pin the
3
+ // nullable state per-file so behavior is identical in both compilation contexts.
4
+ #nullable disable
5
+ namespace DxMessaging.Editor.Analyzers
6
+ {
7
+ using System;
8
+ using System.Collections.Generic;
9
+
10
+ /// <summary>
11
+ /// Pure-BCL data-transfer object for an aggregated per-FQN report row. Mirrors the public
12
+ /// shape of <see cref="BaseCallReportEntry"/> but uses property-style PascalCase fields so it
13
+ /// does not depend on Unity's <c>JsonUtility</c> serialization conventions and is safely
14
+ /// constructible from <c>dotnet test</c>.
15
+ /// </summary>
16
+ /// <remarks>
17
+ /// The harvester wraps every DTO in a <see cref="BaseCallReportEntry"/> (with the lowercase
18
+ /// Unity-serialisable field names) before the snapshot crosses into Editor code. Keep this
19
+ /// shape in lock-step with that wrapper -- fields added here must also flow through to the
20
+ /// Unity-facing entry or the inspector overlay won't see them.
21
+ /// </remarks>
22
+ public sealed class BaseCallReportEntryDto
23
+ {
24
+ /// <summary>Fully-qualified name of the offending type (dot-form for nested types).</summary>
25
+ public string TypeName;
26
+
27
+ /// <summary>Method names whose overrides are missing the corresponding <c>base.*()</c> call.</summary>
28
+ public readonly SortedSet<string> MissingBaseFor = new(StringComparer.Ordinal);
29
+
30
+ /// <summary>Diagnostic IDs that contributed to this entry (e.g., DXMSG006/007/008/009).</summary>
31
+ public readonly HashSet<string> DiagnosticIds = new(StringComparer.Ordinal);
32
+
33
+ /// <summary>Source file path (best-effort) for "Open Script" actions in the inspector overlay.</summary>
34
+ public string FilePath;
35
+
36
+ /// <summary>1-based line number of the first relevant diagnostic, when known.</summary>
37
+ public int Line;
38
+ }
39
+
40
+ /// <summary>
41
+ /// Pure (Unity-API-free) aggregator for the per-assembly merge + retirement logic at the heart
42
+ /// of <see cref="DxMessagingConsoleHarvester"/>. Extracted so the merge contract is covered by
43
+ /// <c>dotnet test</c> via the linked-source pattern (see
44
+ /// <c>WallstopStudios.DxMessaging.SourceGenerators.Tests.csproj</c>).
45
+ /// </summary>
46
+ /// <remarks>
47
+ /// <para>
48
+ /// The harvester must own two pieces of cross-call state to do its job correctly:
49
+ /// </para>
50
+ /// <list type="number">
51
+ /// <item><description><c>typesByAssembly</c>: which FQNs each compiled assembly has reported.
52
+ /// When an assembly recompiles WITHOUT reporting a previously-seen FQN, that FQN is retired
53
+ /// -- the user fixed the offending base call.</description></item>
54
+ /// <item><description><c>mergedReports</c>: the per-FQN union of every assembly's latest
55
+ /// report. The final snapshot merges this with whatever the LogEntries scan yielded.</description></item>
56
+ /// </list>
57
+ /// <para>
58
+ /// Both maps mutate in lock-step inside <see cref="ApplyAssemblyReports"/>. Because retirement
59
+ /// crosses assembly boundaries (an FQN may live in two assemblies; only when neither still
60
+ /// reports it does it disappear from <c>mergedReports</c>), the bookkeeping is the most
61
+ /// failure-prone slice of the harvester. Keeping it pure makes it test-driven.
62
+ /// </para>
63
+ /// </remarks>
64
+ public static class BaseCallReportAggregator
65
+ {
66
+ /// <summary>
67
+ /// Apply a single assembly's freshly-parsed report batch. Replaces that assembly's prior
68
+ /// FQN attribution wholesale (so types the user fixed disappear) and rebuilds the merged
69
+ /// per-FQN map by unioning every assembly's latest reports.
70
+ /// </summary>
71
+ /// <param name="assemblyKey">Stable identifier for the source assembly (typically the
72
+ /// assembly path Unity passes via <c>CompilationPipeline.assemblyCompilationFinished</c>).</param>
73
+ /// <param name="latestReportsForAssembly">Reports parsed from this assembly's most recent
74
+ /// compilation. May be empty -- that case is the retirement path (every FQN this assembly
75
+ /// previously reported is dropped).</param>
76
+ /// <param name="typesByAssembly">Per-assembly FQN bookkeeping. Mutated in place.</param>
77
+ /// <param name="mergedReports">Per-FQN union across every assembly. Mutated in place;
78
+ /// rebuilt from scratch on every call so retirement Just Works regardless of which
79
+ /// assembly drops its claim.</param>
80
+ public static void ApplyAssemblyReports(
81
+ string assemblyKey,
82
+ IReadOnlyDictionary<string, ParsedTypeReport> latestReportsForAssembly,
83
+ Dictionary<string, HashSet<string>> typesByAssembly,
84
+ Dictionary<string, ParsedTypeReport> mergedReports
85
+ )
86
+ {
87
+ if (string.IsNullOrEmpty(assemblyKey))
88
+ {
89
+ throw new ArgumentException("Assembly key must be non-empty.", nameof(assemblyKey));
90
+ }
91
+ if (typesByAssembly is null)
92
+ {
93
+ throw new ArgumentNullException(nameof(typesByAssembly));
94
+ }
95
+ if (mergedReports is null)
96
+ {
97
+ throw new ArgumentNullException(nameof(mergedReports));
98
+ }
99
+
100
+ // 1. Replace this assembly's FQN set with the latest batch. Types absent from the new
101
+ // batch are dropped from the assembly's row; that's the per-assembly retirement.
102
+ if (!typesByAssembly.TryGetValue(assemblyKey, out HashSet<string> typeSet))
103
+ {
104
+ typeSet = new HashSet<string>(StringComparer.Ordinal);
105
+ typesByAssembly[assemblyKey] = typeSet;
106
+ }
107
+ typeSet.Clear();
108
+ if (latestReportsForAssembly is not null)
109
+ {
110
+ foreach (string fqn in latestReportsForAssembly.Keys)
111
+ {
112
+ if (!string.IsNullOrEmpty(fqn))
113
+ {
114
+ typeSet.Add(fqn);
115
+ }
116
+ }
117
+ }
118
+
119
+ // 2. Rebuild mergedReports from the per-assembly view. We can't simply remove "the
120
+ // types this assembly retired" because another assembly may still report them; the
121
+ // only correct algorithm is to start fresh from typesByAssembly + the freshest
122
+ // payload for each (assembly, FQN) pair.
123
+ //
124
+ // Per-FQN merge semantics:
125
+ // - Method list: union, deduplicated ordinally, stored in deterministic
126
+ // ordinal-sorted order.
127
+ // - Diagnostic IDs: union via HashSet.
128
+ // - File path / line: first non-empty wins (stable across recompiles).
129
+ Dictionary<string, ParsedTypeReport> rebuilt = new(StringComparer.Ordinal);
130
+ foreach (KeyValuePair<string, HashSet<string>> assemblyEntry in typesByAssembly)
131
+ {
132
+ string thisAssemblyKey = assemblyEntry.Key;
133
+ HashSet<string> fqns = assemblyEntry.Value;
134
+ if (fqns is null || fqns.Count == 0)
135
+ {
136
+ continue;
137
+ }
138
+
139
+ // For the assembly we just updated, prefer the freshly-parsed payload. For other
140
+ // assemblies, we need the previous merge to still carry their data; but that
141
+ // information is only retrievable from the OUTGOING mergedReports, so we read it
142
+ // before clearing.
143
+ IReadOnlyDictionary<string, ParsedTypeReport> source = string.Equals(
144
+ thisAssemblyKey,
145
+ assemblyKey,
146
+ StringComparison.OrdinalIgnoreCase
147
+ )
148
+ ? latestReportsForAssembly
149
+ : mergedReports;
150
+ if (source is null)
151
+ {
152
+ continue;
153
+ }
154
+
155
+ foreach (string fqn in fqns)
156
+ {
157
+ if (!source.TryGetValue(fqn, out ParsedTypeReport contribution))
158
+ {
159
+ continue;
160
+ }
161
+ if (contribution is null)
162
+ {
163
+ continue;
164
+ }
165
+
166
+ if (!rebuilt.TryGetValue(fqn, out ParsedTypeReport existing))
167
+ {
168
+ // Defensive copy so future ApplyAssemblyReports calls don't mutate state
169
+ // that callers may still hold a reference to.
170
+ existing = new ParsedTypeReport
171
+ {
172
+ TypeFullName = contribution.TypeFullName,
173
+ FilePath = contribution.FilePath,
174
+ Line = contribution.Line,
175
+ };
176
+ foreach (string method in contribution.MissingBaseFor)
177
+ {
178
+ if (!string.IsNullOrEmpty(method))
179
+ {
180
+ existing.MissingBaseFor.Add(method);
181
+ }
182
+ }
183
+ foreach (string id in contribution.DiagnosticIds)
184
+ {
185
+ existing.DiagnosticIds.Add(id);
186
+ }
187
+ rebuilt[fqn] = existing;
188
+ continue;
189
+ }
190
+
191
+ foreach (string method in contribution.MissingBaseFor)
192
+ {
193
+ if (!string.IsNullOrEmpty(method))
194
+ {
195
+ existing.MissingBaseFor.Add(method);
196
+ }
197
+ }
198
+ foreach (string id in contribution.DiagnosticIds)
199
+ {
200
+ existing.DiagnosticIds.Add(id);
201
+ }
202
+ if (
203
+ string.IsNullOrEmpty(existing.FilePath)
204
+ && !string.IsNullOrEmpty(contribution.FilePath)
205
+ )
206
+ {
207
+ existing.FilePath = contribution.FilePath;
208
+ existing.Line = contribution.Line;
209
+ }
210
+ }
211
+ }
212
+
213
+ mergedReports.Clear();
214
+ foreach (KeyValuePair<string, ParsedTypeReport> kvp in rebuilt)
215
+ {
216
+ mergedReports[kvp.Key] = kvp.Value;
217
+ }
218
+ }
219
+
220
+ /// <summary>
221
+ /// Builds the final flat snapshot for the inspector overlay by unioning the LogEntries
222
+ /// scan with the per-assembly merged reports. Both inputs are read-only; the result is a
223
+ /// fresh dictionary the caller owns.
224
+ /// </summary>
225
+ /// <param name="logEntriesReports">Reports harvested from <c>UnityEditor.LogEntries</c>.
226
+ /// May be empty (Unity 2021 path) or null (LogEntries reflection unavailable).</param>
227
+ /// <param name="mergedReports">Per-FQN merged view of every assembly's latest reports
228
+ /// produced by <see cref="ApplyAssemblyReports"/>. May be empty when no compilation
229
+ /// callbacks have fired yet.</param>
230
+ public static Dictionary<string, BaseCallReportEntryDto> BuildSnapshot(
231
+ IReadOnlyDictionary<string, ParsedTypeReport> logEntriesReports,
232
+ IReadOnlyDictionary<string, ParsedTypeReport> mergedReports
233
+ )
234
+ {
235
+ Dictionary<string, BaseCallReportEntryDto> snapshot = new(StringComparer.Ordinal);
236
+
237
+ if (logEntriesReports is not null)
238
+ {
239
+ foreach (KeyValuePair<string, ParsedTypeReport> kvp in logEntriesReports)
240
+ {
241
+ AddOrMerge(snapshot, kvp.Key, kvp.Value);
242
+ }
243
+ }
244
+
245
+ if (mergedReports is not null)
246
+ {
247
+ foreach (KeyValuePair<string, ParsedTypeReport> kvp in mergedReports)
248
+ {
249
+ AddOrMerge(snapshot, kvp.Key, kvp.Value);
250
+ }
251
+ }
252
+
253
+ return snapshot;
254
+ }
255
+
256
+ private static void AddOrMerge(
257
+ Dictionary<string, BaseCallReportEntryDto> snapshot,
258
+ string fqn,
259
+ ParsedTypeReport report
260
+ )
261
+ {
262
+ if (string.IsNullOrEmpty(fqn) || report is null)
263
+ {
264
+ return;
265
+ }
266
+
267
+ if (!snapshot.TryGetValue(fqn, out BaseCallReportEntryDto existing))
268
+ {
269
+ existing = new BaseCallReportEntryDto { TypeName = fqn };
270
+ snapshot[fqn] = existing;
271
+ }
272
+ if (string.IsNullOrEmpty(existing.TypeName))
273
+ {
274
+ existing.TypeName = fqn;
275
+ }
276
+
277
+ foreach (string method in report.MissingBaseFor)
278
+ {
279
+ if (!string.IsNullOrEmpty(method))
280
+ {
281
+ // Dedupe across the dual-source merge: LogEntries and CompilerMessage may
282
+ // both surface the same `<type>.<method>` pair on Unity 2022+, where both
283
+ // pipes are wired. MissingBaseFor is a SortedSet<string>, so duplicates are
284
+ // removed and HelpBox output remains stable via deterministic ordinal sorting.
285
+ existing.MissingBaseFor.Add(method);
286
+ }
287
+ }
288
+
289
+ foreach (string id in report.DiagnosticIds)
290
+ {
291
+ if (!string.IsNullOrEmpty(id))
292
+ {
293
+ // HashSet dedupes repeated IDs surfaced by the dual-source merge
294
+ // (LogEntries + CompilerMessage on Unity 2022+).
295
+ existing.DiagnosticIds.Add(id);
296
+ }
297
+ }
298
+
299
+ // First seen file/line wins so "Open Script" jumps to a stable location across
300
+ // rebuilds; which is what the user's eye lands on first in the console.
301
+ if (string.IsNullOrEmpty(existing.FilePath) && !string.IsNullOrEmpty(report.FilePath))
302
+ {
303
+ existing.FilePath = report.FilePath;
304
+ existing.Line = report.Line;
305
+ }
306
+ }
307
+ }
308
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: f94fb4275c724821995abe36ce51046a
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: