com.wallstop-studios.dxmessaging 2.2.0 → 3.1.0
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.
- package/CHANGELOG.md +315 -67
- package/CHANGELOG.md.meta +7 -7
- package/Editor/Analyzers/BaseCallIlInspector.cs +277 -0
- package/Editor/Analyzers/BaseCallIlInspector.cs.meta +11 -0
- package/Editor/Analyzers/BaseCallLogMessageParser.cs +295 -0
- package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +11 -0
- package/Editor/Analyzers/BaseCallReportAggregator.cs +308 -0
- package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +11 -0
- package/Editor/Analyzers/BaseCallTypeScanner.cs +110 -0
- package/Editor/Analyzers/BaseCallTypeScanner.cs.meta +11 -0
- package/Editor/Analyzers/BaseCallTypeScannerCore.cs +562 -0
- package/Editor/Analyzers/BaseCallTypeScannerCore.cs.meta +11 -0
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +1129 -0
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +11 -0
- package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +44 -44
- package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +44 -44
- package/Editor/Analyzers/System.Collections.Immutable.dll.meta +44 -44
- package/Editor/Analyzers/System.Reflection.Metadata.dll.meta +44 -44
- package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +44 -44
- package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +46 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +72 -72
- package/Editor/Analyzers.meta +8 -8
- package/Editor/AssemblyInfo.cs.meta +11 -3
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +81 -0
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +11 -0
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +429 -0
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +11 -0
- package/Editor/CustomEditors/MessagingComponentEditor.cs +1 -1
- package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +11 -3
- package/Editor/CustomEditors.meta +2 -2
- package/Editor/DxMessagingEditorIdle.cs +62 -0
- package/Editor/DxMessagingEditorIdle.cs.meta +11 -0
- package/Editor/DxMessagingEditorInitializer.cs +112 -15
- package/Editor/DxMessagingEditorInitializer.cs.meta +11 -3
- package/Editor/DxMessagingEditorLog.cs +32 -0
- package/Editor/DxMessagingEditorLog.cs.meta +11 -0
- package/Editor/DxMessagingMenu.cs.meta +11 -11
- package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -11
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +313 -0
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +11 -0
- package/Editor/Settings/DxMessagingSettings.cs +261 -11
- package/Editor/Settings/DxMessagingSettings.cs.meta +11 -3
- package/Editor/Settings/DxMessagingSettingsProvider.cs +50 -33
- package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +11 -3
- package/Editor/Settings.meta +2 -2
- package/Editor/SetupCscRsp.cs +406 -39
- package/Editor/SetupCscRsp.cs.meta +11 -3
- package/Editor/Testing/MessagingComponentEditorHarness.cs +2 -2
- package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +11 -3
- package/Editor/Testing.meta +3 -3
- package/Editor/WallstopStudios.DxMessaging.Editor.asmdef +14 -14
- package/Editor/WallstopStudios.DxMessaging.Editor.asmdef.meta +7 -7
- package/Editor.meta +8 -8
- package/LICENSE.md +9 -9
- package/LICENSE.md.meta +7 -7
- package/README.md +940 -900
- package/README.md.meta +7 -7
- package/Runtime/AssemblyInfo.cs +4 -0
- package/Runtime/AssemblyInfo.cs.meta +11 -3
- package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +11 -3
- package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +11 -3
- package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs +26 -0
- package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +11 -0
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +2 -4
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +11 -3
- package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +11 -3
- package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +11 -3
- package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs +56 -0
- package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs.meta +11 -0
- package/Runtime/Core/Attributes.meta +2 -2
- package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs +195 -0
- package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs.meta +11 -0
- package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs +179 -0
- package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs.meta +11 -0
- package/Runtime/Core/Configuration.meta +9 -0
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +46 -28
- package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +11 -3
- package/Runtime/Core/DataStructure.meta +2 -2
- package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +11 -3
- package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +11 -3
- package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +11 -3
- package/Runtime/Core/Diagnostics.meta +2 -2
- package/Runtime/Core/DxMessagingStaticState.cs +19 -0
- package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -11
- package/Runtime/Core/Extensions/EnumExtensions.cs +6 -5
- package/Runtime/Core/Extensions/EnumExtensions.cs.meta +11 -3
- package/Runtime/Core/Extensions/IListExtensions.cs.meta +11 -3
- package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -12
- package/Runtime/Core/Extensions/MessageExtensions.cs +0 -60
- package/Runtime/Core/Extensions/MessageExtensions.cs.meta +11 -11
- package/Runtime/Core/Extensions.meta +8 -8
- package/Runtime/Core/Helper/MessageCache.cs +32 -0
- package/Runtime/Core/Helper/MessageCache.cs.meta +11 -3
- package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +11 -3
- package/Runtime/Core/Helper.meta +2 -2
- package/Runtime/Core/IMessage.cs +3 -3
- package/Runtime/Core/IMessage.cs.meta +11 -11
- package/Runtime/Core/InstanceId.cs +25 -1
- package/Runtime/Core/InstanceId.cs.meta +11 -11
- package/Runtime/Core/Internal/DxUnsafe.cs +60 -0
- package/Runtime/Core/Internal/DxUnsafe.cs.meta +11 -0
- package/Runtime/Core/Internal/FlatDispatch.cs +198 -0
- package/Runtime/Core/Internal/FlatDispatch.cs.meta +11 -0
- package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs +38 -0
- package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs.meta +11 -0
- package/Runtime/Core/Internal/TypedSlotIndex.cs +81 -0
- package/Runtime/Core/Internal/TypedSlotIndex.cs.meta +11 -0
- package/Runtime/Core/Internal/TypedSlots.cs +597 -0
- package/Runtime/Core/Internal/TypedSlots.cs.meta +11 -0
- package/Runtime/Core/Internal.meta +9 -0
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -11
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -11
- package/Runtime/Core/MessageBus/IMessageBus.cs +189 -15
- package/Runtime/Core/MessageBus/IMessageBus.cs.meta +11 -11
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -11
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +1 -0
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -11
- package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs +16 -0
- package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs +40 -0
- package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/BusSlots.cs +719 -0
- package/Runtime/Core/MessageBus/Internal/BusSlots.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/DispatchKind.cs +38 -0
- package/Runtime/Core/MessageBus/Internal/DispatchKind.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs +20 -0
- package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs +28 -0
- package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs +48 -0
- package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/ISweepable.cs +15 -0
- package/Runtime/Core/MessageBus/Internal/ISweepable.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs +222 -0
- package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal/SlotKey.cs +192 -0
- package/Runtime/Core/MessageBus/Internal/SlotKey.cs.meta +11 -0
- package/Runtime/Core/MessageBus/Internal.meta +9 -0
- package/Runtime/Core/MessageBus/MessageBus.cs +5366 -3838
- package/Runtime/Core/MessageBus/MessageBus.cs.meta +11 -11
- package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -11
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +187 -14
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -11
- package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +11 -11
- package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +11 -11
- package/Runtime/Core/MessageBus.meta +8 -8
- package/Runtime/Core/MessageHandler.cs +2399 -1042
- package/Runtime/Core/MessageHandler.cs.meta +11 -11
- package/Runtime/Core/MessageRegistrationHandle.cs.meta +11 -11
- package/Runtime/Core/MessageRegistrationToken.cs +429 -44
- package/Runtime/Core/MessageRegistrationToken.cs.meta +11 -11
- package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +11 -3
- package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +11 -11
- package/Runtime/Core/Messages/ITargetedMessage.cs.meta +11 -11
- package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +11 -11
- package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +11 -3
- package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +11 -11
- package/Runtime/Core/Messages/StringMessage.cs.meta +11 -3
- package/Runtime/Core/Messages.meta +8 -8
- package/Runtime/Core/MessagingDebug.cs.meta +11 -11
- package/Runtime/Core/Pooling/CollectionPool.cs +266 -0
- package/Runtime/Core/Pooling/CollectionPool.cs.meta +11 -0
- package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs +30 -0
- package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs.meta +11 -0
- package/Runtime/Core/Pooling/DxPools.cs +157 -0
- package/Runtime/Core/Pooling/DxPools.cs.meta +11 -0
- package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs +106 -0
- package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs.meta +11 -0
- package/Runtime/Core/Pooling/IDxMessagingClock.cs +18 -0
- package/Runtime/Core/Pooling/IDxMessagingClock.cs.meta +11 -0
- package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs +55 -0
- package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs.meta +11 -0
- package/Runtime/Core/Pooling/StopwatchClock.cs +27 -0
- package/Runtime/Core/Pooling/StopwatchClock.cs.meta +11 -0
- package/Runtime/Core/Pooling/UnityRealtimeClock.cs +31 -0
- package/Runtime/Core/Pooling/UnityRealtimeClock.cs.meta +11 -0
- package/Runtime/Core/Pooling.meta +9 -0
- package/Runtime/Core.meta +8 -8
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +12 -12
- package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -11
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +12 -12
- package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +11 -3
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +73 -0
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +11 -11
- package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef +20 -20
- package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef.meta +7 -7
- package/Runtime/Unity/Integrations/Reflex.meta +8 -8
- package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +11 -3
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +109 -1
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +11 -11
- package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef +30 -30
- package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef.meta +7 -7
- package/Runtime/Unity/Integrations/VContainer.meta +8 -8
- package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +11 -3
- package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef +30 -30
- package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef.meta +7 -7
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +79 -1
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +11 -11
- package/Runtime/Unity/Integrations/Zenject.meta +8 -8
- package/Runtime/Unity/Integrations.meta +8 -8
- package/Runtime/Unity/MessageAwareComponent.cs +74 -0
- package/Runtime/Unity/MessageAwareComponent.cs.meta +11 -11
- package/Runtime/Unity/MessageBusProviderHandle.cs.meta +12 -12
- package/Runtime/Unity/MessagingComponent.cs +43 -10
- package/Runtime/Unity/MessagingComponent.cs.meta +11 -11
- package/Runtime/Unity/MessagingComponentInstaller.cs.meta +12 -12
- package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +12 -12
- package/Runtime/Unity.meta +8 -8
- package/Runtime/WallstopStudios.DxMessaging.asmdef +14 -14
- package/Runtime/WallstopStudios.DxMessaging.asmdef.meta +7 -7
- package/Runtime.meta +8 -8
- package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab +98 -98
- package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab.meta +7 -7
- package/Samples~/DI/Prefabs.meta +8 -8
- package/Samples~/DI/Providers/GlobalMessageBusProvider.asset +14 -14
- package/Samples~/DI/Providers/GlobalMessageBusProvider.asset.meta +8 -8
- package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset +14 -14
- package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset.meta +8 -8
- package/Samples~/DI/Providers.meta +8 -8
- package/Samples~/DI/README.md +51 -51
- package/Samples~/DI/README.md.meta +7 -7
- package/Samples~/DI/Reflex/SampleInstaller.cs +7 -0
- package/Samples~/DI/Reflex/SampleInstaller.cs.meta +11 -11
- package/Samples~/DI/Reflex.meta +8 -8
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs +6 -1
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +11 -11
- package/Samples~/DI/VContainer.meta +8 -8
- package/Samples~/DI/Zenject/SampleInstaller.cs +8 -0
- package/Samples~/DI/Zenject/SampleInstaller.cs.meta +11 -11
- package/Samples~/DI/Zenject.meta +8 -8
- package/Samples~/DI.meta +8 -8
- package/Samples~/Mini Combat/Boot.cs.meta +11 -11
- package/Samples~/Mini Combat/Enemy.cs.meta +11 -11
- package/Samples~/Mini Combat/Messages.cs.meta +11 -11
- package/Samples~/Mini Combat/Player.cs.meta +11 -11
- package/Samples~/Mini Combat/README.md +324 -323
- package/Samples~/Mini Combat/README.md.meta +7 -7
- package/Samples~/Mini Combat/UIOverlay.cs.meta +11 -11
- package/Samples~/Mini Combat/Walkthrough.md +430 -430
- package/Samples~/Mini Combat/Walkthrough.md.meta +7 -7
- package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef +13 -13
- package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef.meta +7 -7
- package/Samples~/Mini Combat.meta +8 -8
- package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +11 -11
- package/Samples~/UI Buttons + Inspector/Messages.cs.meta +11 -11
- package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +11 -11
- package/Samples~/UI Buttons + Inspector/README.md +210 -209
- package/Samples~/UI Buttons + Inspector/README.md.meta +7 -7
- package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +11 -11
- package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef +13 -13
- package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef.meta +7 -7
- package/Samples~/UI Buttons + Inspector.meta +8 -8
- package/SourceGenerators/Directory.Build.props +50 -3
- package/SourceGenerators/Directory.Build.props.meta +7 -7
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +96 -63
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +11 -11
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +745 -87
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +11 -3
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +39 -46
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj.meta +7 -7
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.meta +8 -8
- package/SourceGenerators/global.json +7 -0
- package/SourceGenerators/global.json.meta +7 -0
- package/SourceGenerators.meta +8 -8
- package/Third Party Notices.md +3 -3
- package/Third Party Notices.md.meta +7 -7
- package/package.json +102 -92
- package/package.json.meta +7 -7
|
@@ -0,0 +1,277 @@
|
|
|
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.Reflection;
|
|
9
|
+
using System.Reflection.Emit;
|
|
10
|
+
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// Pure (Unity-API-free) IL inspector that decides whether a given <see cref="MethodInfo"/>'s
|
|
13
|
+
/// IL body invokes a parent's same-named method (i.e. the IL emit shape of <c>base.X()</c>).
|
|
14
|
+
/// Extracted from <c>BaseCallTypeScanner</c> so the dotnet-test project can cover the byte
|
|
15
|
+
/// walker without depending on Unity APIs.
|
|
16
|
+
/// </summary>
|
|
17
|
+
/// <remarks>
|
|
18
|
+
/// <para>
|
|
19
|
+
/// <b>Why does this exist?</b> The console-scrape harvester is non-deterministic across Unity
|
|
20
|
+
/// 2021 cache hits (Unity skips routing analyzer warnings to <c>LogEntries</c> /
|
|
21
|
+
/// <c>CompilerMessage[]</c> on incremental compiles where Bee/csc reused a cached output).
|
|
22
|
+
/// IL reflection over the loaded assemblies in the AppDomain is deterministic -- the bytes do
|
|
23
|
+
/// not depend on Unity's compile-pipeline state. <see cref="BaseCallTypeScanner"/> uses this
|
|
24
|
+
/// helper to classify every loaded <c>MessageAwareComponent</c> subclass on every domain
|
|
25
|
+
/// reload.
|
|
26
|
+
/// </para>
|
|
27
|
+
/// <para>
|
|
28
|
+
/// <b>Proper OpCodes-table walker.</b> The walker decodes every CIL instruction by looking up
|
|
29
|
+
/// its <see cref="OpCode"/> in the static tables built from <see cref="OpCodes"/> reflection
|
|
30
|
+
/// (single-byte and two-byte 0xFE-prefix forms) and steps the operand-size that the opcode
|
|
31
|
+
/// declares (<see cref="OpCode.OperandType"/>). Misalignment past multi-byte-operand opcodes
|
|
32
|
+
/// (<c>switch</c> jump tables, <c>ldstr</c> 4-byte tokens, 8-byte literal constants, etc.) is
|
|
33
|
+
/// therefore impossible -- the walker either consumes every byte correctly or stops at the
|
|
34
|
+
/// first unrecognised opcode. Phantom DXMSG006 from a misread <c>0x28</c> inside a wider
|
|
35
|
+
/// operand is no longer a failure mode.
|
|
36
|
+
/// </para>
|
|
37
|
+
/// <para>
|
|
38
|
+
/// <b>Defensive bias.</b> When we cannot reason at all (null method, empty name, inaccessible
|
|
39
|
+
/// IL body, <c>GetMethodBody()</c> returns null on abstract / P/Invoke / IL2CPP-stripped
|
|
40
|
+
/// targets, or any reflection exception), the inspector returns <c>true</c>
|
|
41
|
+
/// ("assume clean -- calls base") so the scanner never invents a phantom warning. The
|
|
42
|
+
/// compile-time analyzer is the authoritative source for CI builds (DXMSG006/007/009/010 via
|
|
43
|
+
/// full Roslyn semantic-model precision); the IL scanner exists only to make the editor
|
|
44
|
+
/// overlay light up at edit-time, where a missed warning is far worse than a phantom one.
|
|
45
|
+
/// </para>
|
|
46
|
+
/// </remarks>
|
|
47
|
+
public static class BaseCallIlInspector
|
|
48
|
+
{
|
|
49
|
+
// CIL opcode tables, indexed by the low byte of OpCode.Value. Built once by reflecting over
|
|
50
|
+
// System.Reflection.Emit.OpCodes; every public static OpCode field there represents a
|
|
51
|
+
// canonical CIL instruction. The two-byte form of the table is used when a 0xFE prefix is
|
|
52
|
+
// observed in the IL stream; otherwise we use the single-byte form. Because CIL specifies
|
|
53
|
+
// exactly two prefix bytes (single-byte = direct, two-byte = 0xFE prefix), this division
|
|
54
|
+
// covers every defined opcode.
|
|
55
|
+
private static readonly OpCode[] s_singleByteOps = BuildOpCodeTable(twoByte: false);
|
|
56
|
+
private static readonly OpCode[] s_twoByteOps = BuildOpCodeTable(twoByte: true);
|
|
57
|
+
|
|
58
|
+
private static OpCode[] BuildOpCodeTable(bool twoByte)
|
|
59
|
+
{
|
|
60
|
+
OpCode[] table = new OpCode[256];
|
|
61
|
+
foreach (
|
|
62
|
+
FieldInfo field in typeof(OpCodes).GetFields(
|
|
63
|
+
BindingFlags.Public | BindingFlags.Static
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
{
|
|
67
|
+
if (field.GetValue(null) is not OpCode op)
|
|
68
|
+
{
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
ushort value = (ushort)op.Value;
|
|
72
|
+
bool isTwoByte = (value & 0xFF00) == 0xFE00;
|
|
73
|
+
bool isSingleByte = (value & 0xFF00) == 0;
|
|
74
|
+
if (twoByte && isTwoByte)
|
|
75
|
+
{
|
|
76
|
+
table[value & 0xFF] = op;
|
|
77
|
+
}
|
|
78
|
+
else if (!twoByte && isSingleByte)
|
|
79
|
+
{
|
|
80
|
+
table[value & 0xFF] = op;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return table;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// <summary>
|
|
87
|
+
/// Returns <c>true</c> if <paramref name="method"/>'s IL body contains a
|
|
88
|
+
/// <c>call</c>/<c>callvirt</c> to a parent type's same-named method. Defensive:
|
|
89
|
+
/// returns <c>true</c> (assume-clean) if the IL body is null/inaccessible to avoid
|
|
90
|
+
/// false-positive warnings on platforms or methods where reflection on bodies is
|
|
91
|
+
/// restricted (abstract methods, P/Invoke, IL2CPP-stripped bodies, etc.).
|
|
92
|
+
/// </summary>
|
|
93
|
+
/// <param name="method">The override on the descendant type whose IL we wish to inspect.</param>
|
|
94
|
+
/// <param name="methodName">The expected base method name (e.g. <c>"OnEnable"</c>).</param>
|
|
95
|
+
/// <returns>
|
|
96
|
+
/// <c>true</c> if the IL contains a base-call shape, OR the IL was inaccessible (safe
|
|
97
|
+
/// default -- assume clean). <c>false</c> only when IL was readable AND no call/callvirt
|
|
98
|
+
/// targeting a parent same-named method was found.
|
|
99
|
+
/// </returns>
|
|
100
|
+
public static bool MethodIlContainsBaseCall(MethodInfo method, string methodName)
|
|
101
|
+
{
|
|
102
|
+
if (method == null || string.IsNullOrEmpty(methodName))
|
|
103
|
+
{
|
|
104
|
+
// Defensive: treat as clean when we don't have enough information to reason. This
|
|
105
|
+
// ensures the scanner never emits a phantom warning on a degenerate input.
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try
|
|
110
|
+
{
|
|
111
|
+
MethodBody body = method.GetMethodBody();
|
|
112
|
+
if (body == null)
|
|
113
|
+
{
|
|
114
|
+
// Abstract / extern / runtime-implemented / IL2CPP-stripped; cannot inspect.
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
byte[] il = body.GetILAsByteArray();
|
|
119
|
+
if (il == null || il.Length == 0)
|
|
120
|
+
{
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Module module = method.Module;
|
|
125
|
+
Type[] genericTypeArgs =
|
|
126
|
+
method.DeclaringType?.IsGenericType == true
|
|
127
|
+
? method.DeclaringType.GetGenericArguments()
|
|
128
|
+
: null;
|
|
129
|
+
Type[] genericMethodArgs = method.IsGenericMethod
|
|
130
|
+
? method.GetGenericArguments()
|
|
131
|
+
: null;
|
|
132
|
+
|
|
133
|
+
int i = 0;
|
|
134
|
+
while (i < il.Length)
|
|
135
|
+
{
|
|
136
|
+
OpCode op;
|
|
137
|
+
if (il[i] == 0xFE)
|
|
138
|
+
{
|
|
139
|
+
// Two-byte (0xFE-prefixed) opcode. Without a following byte we cannot
|
|
140
|
+
// decode the instruction; bail out conservatively. Truncated IL is not a
|
|
141
|
+
// shape Roslyn ever emits, so reaching this path means we mis-stepped and
|
|
142
|
+
// the safest answer is the assume-clean default.
|
|
143
|
+
if (i + 1 >= il.Length)
|
|
144
|
+
{
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
op = s_twoByteOps[il[i + 1]];
|
|
148
|
+
i += 2;
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
{
|
|
152
|
+
op = s_singleByteOps[il[i]];
|
|
153
|
+
i += 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Unrecognised opcode (zero-initialised slot in the table); abandon the walk
|
|
157
|
+
// rather than risk the rest of the stream getting misread. Returning the
|
|
158
|
+
// assume-clean default keeps the scanner from inventing a phantom warning.
|
|
159
|
+
if (op.Size == 0)
|
|
160
|
+
{
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (op == OpCodes.Call || op == OpCodes.Callvirt)
|
|
165
|
+
{
|
|
166
|
+
if (i + 4 > il.Length)
|
|
167
|
+
{
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
int token = BitConverter.ToInt32(il, i);
|
|
171
|
+
try
|
|
172
|
+
{
|
|
173
|
+
MethodBase target = module.ResolveMethod(
|
|
174
|
+
token,
|
|
175
|
+
genericTypeArgs,
|
|
176
|
+
genericMethodArgs
|
|
177
|
+
);
|
|
178
|
+
if (
|
|
179
|
+
target != null
|
|
180
|
+
&& string.Equals(target.Name, methodName, StringComparison.Ordinal)
|
|
181
|
+
)
|
|
182
|
+
{
|
|
183
|
+
Type declaring = method.DeclaringType;
|
|
184
|
+
Type resolved = target.DeclaringType;
|
|
185
|
+
// Guard against false-positives: the resolved method must live on a
|
|
186
|
+
// STRICT base type of the declaring class (not the declaring class
|
|
187
|
+
// itself, not a sibling, not a generic-arg shadow). IsAssignableFrom
|
|
188
|
+
// checks "is `declaring` assignable TO `resolved`"; i.e. is
|
|
189
|
+
// `resolved` an ancestor of `declaring`.
|
|
190
|
+
if (
|
|
191
|
+
declaring != null
|
|
192
|
+
&& resolved != null
|
|
193
|
+
&& declaring != resolved
|
|
194
|
+
&& resolved.IsAssignableFrom(declaring)
|
|
195
|
+
)
|
|
196
|
+
{
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch
|
|
202
|
+
{
|
|
203
|
+
// ResolveMethod throws on tokens that don't bind in our generic-arg
|
|
204
|
+
// context (e.g. a MemberRef into a closed generic we can't resolve).
|
|
205
|
+
// The OpCodes-table walker means we can no longer land on a misaligned
|
|
206
|
+
// 0x28 inside a wider operand, so this catch only protects against
|
|
207
|
+
// legitimate-but-unbindable tokens; we swallow and continue scanning.
|
|
208
|
+
}
|
|
209
|
+
i += 4;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Step over the operand based on the opcode's declared operand type. Every
|
|
214
|
+
// CIL operand size is decided by OperandType, which is exactly why the table
|
|
215
|
+
// walker is misalignment-proof.
|
|
216
|
+
i += GetOperandSize(op, il, i);
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
catch
|
|
221
|
+
{
|
|
222
|
+
// Any reflection failure → assume clean. We never want the scanner itself to
|
|
223
|
+
// become the source of a phantom warning.
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Returns the number of operand bytes that follow an opcode of the given OperandType,
|
|
229
|
+
// given the operand-start offset (needed for InlineSwitch's variable-length jump table).
|
|
230
|
+
private static int GetOperandSize(OpCode op, byte[] il, int operandStart)
|
|
231
|
+
{
|
|
232
|
+
switch (op.OperandType)
|
|
233
|
+
{
|
|
234
|
+
case OperandType.InlineNone:
|
|
235
|
+
return 0;
|
|
236
|
+
case OperandType.ShortInlineBrTarget:
|
|
237
|
+
case OperandType.ShortInlineI:
|
|
238
|
+
case OperandType.ShortInlineVar:
|
|
239
|
+
return 1;
|
|
240
|
+
case OperandType.InlineVar:
|
|
241
|
+
return 2;
|
|
242
|
+
case OperandType.InlineBrTarget:
|
|
243
|
+
case OperandType.InlineField:
|
|
244
|
+
case OperandType.InlineI:
|
|
245
|
+
case OperandType.InlineMethod:
|
|
246
|
+
case OperandType.InlineSig:
|
|
247
|
+
case OperandType.InlineString:
|
|
248
|
+
case OperandType.InlineTok:
|
|
249
|
+
case OperandType.InlineType:
|
|
250
|
+
case OperandType.ShortInlineR:
|
|
251
|
+
return 4;
|
|
252
|
+
case OperandType.InlineI8:
|
|
253
|
+
case OperandType.InlineR:
|
|
254
|
+
return 8;
|
|
255
|
+
case OperandType.InlineSwitch:
|
|
256
|
+
// 4-byte case count, then N × 4-byte branch targets. Truncated stream → bail
|
|
257
|
+
// by consuming the rest defensively (the outer loop's bounds check then ends
|
|
258
|
+
// the walk).
|
|
259
|
+
if (operandStart + 4 > il.Length)
|
|
260
|
+
{
|
|
261
|
+
return il.Length - operandStart;
|
|
262
|
+
}
|
|
263
|
+
int caseCount = BitConverter.ToInt32(il, operandStart);
|
|
264
|
+
if (caseCount < 0)
|
|
265
|
+
{
|
|
266
|
+
// Negative case-count is malformed IL; bail conservatively.
|
|
267
|
+
return il.Length - operandStart;
|
|
268
|
+
}
|
|
269
|
+
return 4 + caseCount * 4;
|
|
270
|
+
default:
|
|
271
|
+
// Unknown OperandType; bail conservatively by consuming the rest of the
|
|
272
|
+
// stream so the outer loop terminates without misaligning further.
|
|
273
|
+
return il.Length - operandStart;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -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><Compile Include="...\BaseCallLogMessageParser.cs" Link="..." /></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
|
+
}
|