com.wallstop-studios.dxmessaging 2.1.1 → 2.1.3
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/.github/workflows/dotnet-tests.yml +72 -0
- package/.lychee.toml +4 -2
- package/AGENTS.md +13 -12
- package/Docs/Comparisons.md +5 -5
- package/Docs/Install.md +2 -1
- package/Docs/InterceptorsAndOrdering.md +1 -1
- package/Docs/Performance.md +15 -13
- package/Docs/QuickReference.md +1 -1
- package/Docs/Reference.md +5 -5
- package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +13 -2
- package/Editor/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
- package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +11 -0
- package/Editor/Analyzers/System.Collections.Immutable.dll +0 -0
- package/Editor/Analyzers/System.Collections.Immutable.dll.meta +11 -0
- package/Editor/Analyzers/System.Reflection.Metadata.dll +0 -0
- package/Editor/Analyzers/System.Reflection.Metadata.dll.meta +13 -2
- package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
- package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +11 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +3 -2
- package/Editor/AssemblyInfo.cs +3 -0
- package/Editor/AssemblyInfo.cs.meta +3 -0
- package/Editor/CustomEditors/MessagingComponentEditor.cs +24 -0
- package/Editor/DxMessagingEditorInitializer.cs +58 -1
- package/Editor/DxMessagingMenu.cs +38 -0
- package/Editor/DxMessagingMenu.cs.meta +11 -0
- package/Editor/DxMessagingSceneBuildProcessor.cs +81 -0
- package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -0
- package/Editor/Settings/DxMessagingSettings.cs +37 -6
- package/Editor/Settings/DxMessagingSettingsProvider.cs +45 -7
- package/Editor/SetupCscRsp.cs +133 -53
- package/Editor/Testing/MessagingComponentEditorHarness.cs +218 -0
- package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +3 -0
- package/Editor/Testing.meta +3 -0
- package/README.md +10 -4
- package/Runtime/AssemblyInfo.cs +1 -0
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +52 -0
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +16 -0
- package/Runtime/Core/Diagnostics/MessageEmissionData.cs +27 -12
- package/Runtime/Core/Diagnostics/MessageRegistrationType.cs +62 -0
- package/Runtime/Core/DxMessagingStaticState.cs +108 -0
- package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -0
- package/Runtime/Core/Extensions/IListExtensions.cs +24 -0
- package/Runtime/Core/Extensions/MessageBusExtensions.cs +144 -2
- package/Runtime/Core/Extensions/MessageExtensions.cs +2 -2
- package/Runtime/Core/Helper/MessageCache.cs +16 -0
- package/Runtime/Core/Helper/MessageHelperIndexer.cs +77 -0
- package/Runtime/Core/InstanceId.cs +91 -3
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs +31 -0
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -0
- package/Runtime/Core/MessageBus/IMessageBus.cs +44 -16
- package/Runtime/Core/MessageBus/MessageBus.cs +96 -25
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +46 -2
- package/Runtime/Core/MessageBus/MessagingRegistration.cs +63 -5
- package/Runtime/Core/MessageBus/RegistrationLog.cs +10 -0
- package/Runtime/Core/MessageHandler.cs +141 -8
- package/Runtime/Core/MessageRegistrationHandle.cs +59 -0
- package/Runtime/Core/MessageRegistrationToken.cs +20 -4
- package/Runtime/Core/Messages/ReflexiveMessage.cs +38 -0
- package/Runtime/Core/MessagingDebug.cs +16 -1
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +6 -0
- package/Runtime/Unity/DxMessagingRuntimeInitializer.cs +19 -0
- package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -0
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +6 -0
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +19 -0
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +10 -0
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +14 -0
- package/Runtime/Unity/MessageAwareComponent.cs +2 -0
- package/Runtime/Unity/MessageBusProviderHandle.cs +4 -0
- package/Runtime/Unity/MessagingComponent.cs +109 -0
- package/Runtime/Unity/MessagingComponentInstaller.cs +2 -0
- package/Runtime/Unity/ScriptableMessageBusProvider.cs +2 -0
- package/Samples~/DI/README.md +13 -13
- package/Samples~/Mini Combat/README.md +15 -15
- package/Samples~/Mini Combat/Walkthrough.md +12 -12
- package/Samples~/UI Buttons + Inspector/README.md +4 -4
- package/SourceGenerators/Directory.Build.props +9 -0
- package/{Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj.meta → SourceGenerators/Directory.Build.props.meta} +1 -1
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +23 -24
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +91 -27
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +24 -4
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DocsSnippetCompilationTests.cs +193 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DocsSnippetCompilationTests.cs.meta +11 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DxAutoConstructorGeneratorDiagnosticsTests.cs +69 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DxAutoConstructorGeneratorDiagnosticsTests.cs.meta +11 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DxMessageIdGeneratorDiagnosticsTests.cs +66 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/DxMessageIdGeneratorDiagnosticsTests.cs.meta +11 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/GeneratorTestUtilities.cs +155 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/GeneratorTestUtilities.cs.meta +11 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/WallstopStudios.DxMessaging.SourceGenerators.Tests.csproj +20 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests/WallstopStudios.DxMessaging.SourceGenerators.Tests.csproj.meta +7 -0
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.Tests.meta +8 -0
- package/Tests/Editor/MessagingComponentEditorHarnessTests.cs +243 -0
- package/Tests/Editor/MessagingComponentEditorHarnessTests.cs.meta +3 -0
- package/Tests/Editor/MessagingComponentSerializationTests.cs +129 -0
- package/Tests/Editor/MessagingComponentSerializationTests.cs.meta +3 -0
- package/Tests/Editor/WallstopStudios.DxMessaging.Tests.Editor.asmdef +19 -0
- package/Tests/Editor/WallstopStudios.DxMessaging.Tests.Editor.asmdef.meta +3 -0
- package/Tests/Editor.meta +3 -0
- package/Tests/Runtime/Benchmarks/BenchmarkSession.cs +3 -0
- package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs +3 -0
- package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs +3 -0
- package/Tests/Runtime/Benchmarks/PerformanceTests.cs +137 -0
- package/Tests/Runtime/Core/AlternateBusTests.cs +3 -0
- package/Tests/Runtime/Core/BroadcastTests.cs +3 -0
- package/Tests/Runtime/Core/CyclicBufferTests.cs +3 -0
- package/Tests/Runtime/Core/DefaultBusFallbackTests.cs +5 -2
- package/Tests/Runtime/Core/DiagnosticsTests.cs +6 -3
- package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs +69 -0
- package/Tests/Runtime/Core/DxMessagingStaticStateTests.cs.meta +11 -0
- package/Tests/Runtime/Core/EdgeCaseTests.cs +3 -0
- package/Tests/Runtime/Core/EnablementTests.cs +3 -0
- package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs +2 -2
- package/Tests/Runtime/Core/GenericMessageTests.cs +3 -0
- package/Tests/Runtime/Core/GlobalAcceptAllTests.cs +3 -0
- package/Tests/Runtime/Core/InterceptorCancellationTests.cs +3 -0
- package/Tests/Runtime/Core/LifecycleTests.cs +3 -0
- package/Tests/Runtime/Core/MessageEmissionDataTests.cs +70 -0
- package/Tests/Runtime/Core/MessageEmissionDataTests.cs.meta +11 -0
- package/Tests/Runtime/Core/MessagingComponentLifecycleTests.cs +3 -0
- package/Tests/Runtime/Core/MessagingTestBase.cs +3 -0
- package/Tests/Runtime/Core/MutationDedupeTests.cs +3 -0
- package/Tests/Runtime/Core/MutationDestructionTests.cs +3 -0
- package/Tests/Runtime/Core/MutationDuringEmissionTests.cs +3 -0
- package/Tests/Runtime/Core/MutationGlobalAddTests.cs +3 -0
- package/Tests/Runtime/Core/MutationInterceptorTests.cs +3 -0
- package/Tests/Runtime/Core/MutationPostProcessorAcrossHandlersTests.cs +3 -0
- package/Tests/Runtime/Core/MutationPostProcessorMoreTests.cs +3 -0
- package/Tests/Runtime/Core/MutationPriorityTests.cs +3 -0
- package/Tests/Runtime/Core/NominalTests.cs +3 -0
- package/Tests/Runtime/Core/OrderingTests.cs +3 -0
- package/Tests/Runtime/Core/OverDeregistrationTests.cs +3 -0
- package/Tests/Runtime/Core/PostProcessorTests.cs +3 -0
- package/Tests/Runtime/Core/ReflexiveErrorTests.cs +3 -0
- package/Tests/Runtime/Core/ReflexiveMessageWarningTests.cs +4 -1
- package/Tests/Runtime/Core/ReflexiveTests.cs +3 -0
- package/Tests/Runtime/Core/RegistrationTests.cs +3 -0
- package/Tests/Runtime/Core/StringShorthandTests.cs +3 -0
- package/Tests/Runtime/Core/TargetedTests.cs +3 -0
- package/Tests/Runtime/Core/TypedShorthandTests.cs +3 -0
- package/Tests/Runtime/Core/UntargetedEquivalenceTests.cs +3 -0
- package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs +14 -78
- package/Tests/Runtime/Core/UntargetedTests.cs +3 -0
- package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs +4 -1
- package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs +3 -0
- package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs +3 -0
- package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs +3 -0
- package/Tests/Runtime/Scripts/Components/ManualListenerComponent.cs +3 -0
- package/Tests/Runtime/Scripts/Components/ReflexiveReceiverComponent.cs +3 -0
- package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs +3 -0
- package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs +3 -0
- package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs +87 -3
- package/Tests/Runtime/Unity/MessagingComponentInstallerSceneTests.cs +109 -0
- package/Tests/Runtime/Unity/MessagingComponentInstallerSceneTests.cs.meta +11 -0
- package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs +159 -17
- package/package.json +1 -1
- package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj +0 -7
package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs
CHANGED
|
@@ -56,6 +56,10 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
56
56
|
ImmutableArray<IFieldSymbol> FieldsToInject // Public readonly non-static fields
|
|
57
57
|
);
|
|
58
58
|
|
|
59
|
+
/// <summary>
|
|
60
|
+
/// Configures the incremental generator pipeline that discovers annotated types and emits constructors.
|
|
61
|
+
/// </summary>
|
|
62
|
+
/// <param name="context">Initialization context provided by Roslyn.</param>
|
|
59
63
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
60
64
|
{
|
|
61
65
|
// Find all class/struct/record declarations that have attribute lists
|
|
@@ -208,7 +212,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
208
212
|
// Location-specific suggestions on each non-partial container
|
|
209
213
|
foreach (INamedTypeSymbol container in nonPartial)
|
|
210
214
|
{
|
|
211
|
-
SyntaxReference
|
|
215
|
+
SyntaxReference sr =
|
|
212
216
|
container.DeclaringSyntaxReferences.FirstOrDefault();
|
|
213
217
|
if (sr != null && sr.GetSyntax() is TypeDeclarationSyntax tds)
|
|
214
218
|
{
|
|
@@ -258,11 +262,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
258
262
|
? string.Empty
|
|
259
263
|
: $"namespace {namespaceName}\n{{";
|
|
260
264
|
string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
|
|
261
|
-
const string
|
|
265
|
+
const string Indent = " ";
|
|
262
266
|
|
|
263
267
|
// Build container wrappers for nested types so the partial can merge correctly
|
|
264
268
|
var containers = new Stack<INamedTypeSymbol>();
|
|
265
|
-
INamedTypeSymbol
|
|
269
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
266
270
|
while (current is not null)
|
|
267
271
|
{
|
|
268
272
|
containers.Push(current);
|
|
@@ -271,7 +275,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
271
275
|
|
|
272
276
|
var containersOpen = new StringBuilder();
|
|
273
277
|
var containersClose = new StringBuilder();
|
|
274
|
-
string currentIndent =
|
|
278
|
+
string currentIndent = Indent; // one level inside namespace (or top-level)
|
|
275
279
|
|
|
276
280
|
foreach (INamedTypeSymbol container in containers)
|
|
277
281
|
{
|
|
@@ -306,7 +310,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
306
310
|
$"{currentIndent}{containerAccessibility} partial {containerKind} {container.Name}{containerTypeParams}"
|
|
307
311
|
);
|
|
308
312
|
containersOpen.Append(currentIndent).AppendLine("{");
|
|
309
|
-
currentIndent +=
|
|
313
|
+
currentIndent += Indent;
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
string innerIndent = currentIndent; // indent level for the target (innermost) type
|
|
@@ -350,13 +354,8 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
350
354
|
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
|
|
351
355
|
);
|
|
352
356
|
|
|
353
|
-
List<(
|
|
354
|
-
string Type,
|
|
355
|
-
string Name,
|
|
356
|
-
bool IsOptional,
|
|
357
|
-
string? DefaultExpr
|
|
358
|
-
)> parameterDetails =
|
|
359
|
-
new List<(string Type, string Name, bool IsOptional, string? DefaultExpr)>();
|
|
357
|
+
List<(string Type, string Name, bool IsOptional, string DefaultExpr)> parameterDetails =
|
|
358
|
+
new List<(string Type, string Name, bool IsOptional, string DefaultExpr)>();
|
|
360
359
|
|
|
361
360
|
// For validating expressions, use the semantic model for this type's tree
|
|
362
361
|
SemanticModel semanticModel = compilation.GetSemanticModel(
|
|
@@ -368,7 +367,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
368
367
|
{
|
|
369
368
|
string fieldType = field.Type.ToDisplayString(fieldTypeFormat);
|
|
370
369
|
string fieldName = field.Name;
|
|
371
|
-
string
|
|
370
|
+
string defaultExpr = null;
|
|
372
371
|
bool isOptional = false;
|
|
373
372
|
|
|
374
373
|
foreach (AttributeData attr in field.GetAttributes())
|
|
@@ -455,10 +454,10 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
455
454
|
}
|
|
456
455
|
else if (arg.Kind == TypedConstantKind.Primitive)
|
|
457
456
|
{
|
|
458
|
-
object
|
|
457
|
+
object val = arg.Value;
|
|
459
458
|
defaultExpr = FormatLiteral(val, arg.Type);
|
|
460
459
|
// Validate primitive conversion to field type
|
|
461
|
-
ITypeSymbol
|
|
460
|
+
ITypeSymbol sourceType = arg.Type;
|
|
462
461
|
if (sourceType != null)
|
|
463
462
|
{
|
|
464
463
|
Conversion conv = compilation.ClassifyConversion(
|
|
@@ -489,12 +488,12 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
489
488
|
}
|
|
490
489
|
|
|
491
490
|
parameterDetails.Add((fieldType, fieldName, isOptional, defaultExpr));
|
|
492
|
-
constructorBody.AppendLine($"{
|
|
491
|
+
constructorBody.AppendLine($"{Indent}{Indent} this.{fieldName} = {fieldName};");
|
|
493
492
|
}
|
|
494
493
|
|
|
495
494
|
for (int i = 0; i < parameterDetails.Count; i++)
|
|
496
495
|
{
|
|
497
|
-
(string Type, string Name, bool IsOptional, string
|
|
496
|
+
(string Type, string Name, bool IsOptional, string DefaultExpr) p =
|
|
498
497
|
parameterDetails[i];
|
|
499
498
|
constructorParams.Append($"{p.Type} {p.Name}");
|
|
500
499
|
if (p.IsOptional)
|
|
@@ -520,7 +519,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
520
519
|
{
|
|
521
520
|
currentIndent = currentIndent.Substring(
|
|
522
521
|
0,
|
|
523
|
-
Math.Max(0, currentIndent.Length -
|
|
522
|
+
Math.Max(0, currentIndent.Length - Indent.Length)
|
|
524
523
|
);
|
|
525
524
|
containersClose.Append(currentIndent).AppendLine("}");
|
|
526
525
|
}
|
|
@@ -533,9 +532,9 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
533
532
|
{{namespaceBlockOpen}}
|
|
534
533
|
{{containersOpen}}{{innerIndent}}{{typeAccessibility}} partial {{typeKind}} {{typeName}}
|
|
535
534
|
{{innerIndent}}{
|
|
536
|
-
{{
|
|
537
|
-
{{
|
|
538
|
-
{{
|
|
535
|
+
{{Indent}} /// <summary>
|
|
536
|
+
{{Indent}} /// Auto-generated constructor by DxAutoGenConstructorGenerator.
|
|
537
|
+
{{Indent}} /// </summary>
|
|
539
538
|
{{innerIndent}} {{constructorAccessibility}} {{typeSymbol.Name}}({{constructorParams}})
|
|
540
539
|
{{innerIndent}} {
|
|
541
540
|
{{constructorBody}}
|
|
@@ -546,7 +545,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
546
545
|
""";
|
|
547
546
|
}
|
|
548
547
|
|
|
549
|
-
private static string FormatLiteral(object
|
|
548
|
+
private static string FormatLiteral(object value, ITypeSymbol type)
|
|
550
549
|
{
|
|
551
550
|
if (value == null)
|
|
552
551
|
{
|
|
@@ -635,7 +634,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
635
634
|
SpeculativeBindingOption.BindAsExpression
|
|
636
635
|
);
|
|
637
636
|
|
|
638
|
-
ITypeSymbol
|
|
637
|
+
ITypeSymbol sourceType = typeInfo.Type;
|
|
639
638
|
if (sourceType == null)
|
|
640
639
|
{
|
|
641
640
|
// Could not bind; let the compiler decide but report as invalid here
|
|
@@ -655,7 +654,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
655
654
|
private static List<INamedTypeSymbol> GetNonPartialContainers(INamedTypeSymbol typeSymbol)
|
|
656
655
|
{
|
|
657
656
|
List<INamedTypeSymbol> result = new();
|
|
658
|
-
INamedTypeSymbol
|
|
657
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
659
658
|
while (current is not null)
|
|
660
659
|
{
|
|
661
660
|
if (!IsDeclaredFullyPartial(current))
|
package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs
CHANGED
|
@@ -66,9 +66,14 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
66
66
|
private record struct MessageToGenerateInfo(
|
|
67
67
|
INamedTypeSymbol TypeSymbol,
|
|
68
68
|
TypeDeclarationSyntax DeclarationSyntax,
|
|
69
|
-
string TargetInterfaceFullName
|
|
69
|
+
string TargetInterfaceFullName,
|
|
70
|
+
bool HasConflictingMessageAttributes
|
|
70
71
|
);
|
|
71
72
|
|
|
73
|
+
/// <summary>
|
|
74
|
+
/// Configures the incremental generator pipeline that assigns deterministic message identifiers.
|
|
75
|
+
/// </summary>
|
|
76
|
+
/// <param name="context">Initialization context provided by Roslyn.</param>
|
|
72
77
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
73
78
|
{
|
|
74
79
|
// Find all class/struct/record declarations with attributes
|
|
@@ -143,15 +148,15 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
143
148
|
return null; // Cannot be a concrete message type
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
string
|
|
151
|
+
string foundTargetInterface = null;
|
|
147
152
|
bool multipleAttributes = false;
|
|
148
153
|
|
|
149
154
|
// Check attributes to find the specific message type (Broadcast, Targeted, etc.)
|
|
150
155
|
foreach (AttributeData attributeData in typeSymbol.GetAttributes())
|
|
151
156
|
{
|
|
152
157
|
cancellationToken.ThrowIfCancellationRequested();
|
|
153
|
-
string
|
|
154
|
-
string
|
|
158
|
+
string currentAttributeFullName = attributeData.AttributeClass?.ToDisplayString();
|
|
159
|
+
string targetInterfaceForThisAttribute = null;
|
|
155
160
|
|
|
156
161
|
switch (currentAttributeFullName)
|
|
157
162
|
{
|
|
@@ -180,17 +185,21 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
|
|
183
|
-
if (multipleAttributes
|
|
188
|
+
if (multipleAttributes)
|
|
189
|
+
{
|
|
190
|
+
foundTargetInterface = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (foundTargetInterface == null && !multipleAttributes)
|
|
184
194
|
{
|
|
185
|
-
// Don't return info if multiple different message attrs or none found.
|
|
186
|
-
// The Execute method will report the error for multiple attributes later.
|
|
187
195
|
return null;
|
|
188
196
|
}
|
|
189
197
|
|
|
190
198
|
return new MessageToGenerateInfo(
|
|
191
199
|
typeSymbol,
|
|
192
200
|
typeDeclarationSyntax,
|
|
193
|
-
foundTargetInterface
|
|
201
|
+
foundTargetInterface,
|
|
202
|
+
multipleAttributes
|
|
194
203
|
);
|
|
195
204
|
}
|
|
196
205
|
|
|
@@ -206,18 +215,19 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
206
215
|
}
|
|
207
216
|
|
|
208
217
|
// --- Step 1: Filter out types with multiple attributes applied ---
|
|
209
|
-
Dictionary<ISymbol, MessageToGenerateInfo> uniqueTypes = new
|
|
218
|
+
Dictionary<ISymbol, MessageToGenerateInfo> uniqueTypes = new Dictionary<
|
|
219
|
+
ISymbol,
|
|
220
|
+
MessageToGenerateInfo
|
|
221
|
+
>(SymbolEqualityComparer.Default);
|
|
222
|
+
HashSet<ISymbol> conflictingTypes = new HashSet<ISymbol>(
|
|
210
223
|
SymbolEqualityComparer.Default
|
|
211
224
|
);
|
|
212
|
-
HashSet<ISymbol> typesWithMultipleAttributes = new(SymbolEqualityComparer.Default);
|
|
213
225
|
|
|
214
226
|
foreach (MessageToGenerateInfo typeInfo in typesToGenerate)
|
|
215
227
|
{
|
|
216
|
-
if (
|
|
228
|
+
if (typeInfo.HasConflictingMessageAttributes)
|
|
217
229
|
{
|
|
218
|
-
|
|
219
|
-
// This implies multiple different valid attributes were found, report error.
|
|
220
|
-
if (typesWithMultipleAttributes.Add(typeInfo.TypeSymbol)) // Report only once
|
|
230
|
+
if (conflictingTypes.Add(typeInfo.TypeSymbol))
|
|
221
231
|
{
|
|
222
232
|
context.ReportDiagnostic(
|
|
223
233
|
Diagnostic.Create(
|
|
@@ -226,7 +236,44 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
226
236
|
typeInfo.TypeSymbol.ToDisplayString()
|
|
227
237
|
)
|
|
228
238
|
);
|
|
229
|
-
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (conflictingTypes.Contains(typeInfo.TypeSymbol))
|
|
245
|
+
{
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (typeInfo.TargetInterfaceFullName is null)
|
|
250
|
+
{
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
uniqueTypes.TryGetValue(
|
|
256
|
+
typeInfo.TypeSymbol,
|
|
257
|
+
out MessageToGenerateInfo existingInfo
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
{
|
|
261
|
+
if (
|
|
262
|
+
!string.Equals(
|
|
263
|
+
existingInfo.TargetInterfaceFullName,
|
|
264
|
+
typeInfo.TargetInterfaceFullName,
|
|
265
|
+
StringComparison.Ordinal
|
|
266
|
+
) && conflictingTypes.Add(typeInfo.TypeSymbol)
|
|
267
|
+
)
|
|
268
|
+
{
|
|
269
|
+
context.ReportDiagnostic(
|
|
270
|
+
Diagnostic.Create(
|
|
271
|
+
MultipleAttributesError,
|
|
272
|
+
typeInfo.DeclarationSyntax.Identifier.GetLocation(),
|
|
273
|
+
typeInfo.TypeSymbol.ToDisplayString()
|
|
274
|
+
)
|
|
275
|
+
);
|
|
276
|
+
uniqueTypes.Remove(typeInfo.TypeSymbol);
|
|
230
277
|
}
|
|
231
278
|
}
|
|
232
279
|
else
|
|
@@ -235,10 +282,21 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
235
282
|
}
|
|
236
283
|
}
|
|
237
284
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
285
|
+
if (uniqueTypes.Count == 0)
|
|
286
|
+
{
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
List<MessageToGenerateInfo> validSingleAttrTypes = new List<MessageToGenerateInfo>();
|
|
291
|
+
foreach (KeyValuePair<ISymbol, MessageToGenerateInfo> entry in uniqueTypes)
|
|
292
|
+
{
|
|
293
|
+
if (conflictingTypes.Contains(entry.Key))
|
|
294
|
+
{
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
validSingleAttrTypes.Add(entry.Value);
|
|
299
|
+
}
|
|
242
300
|
|
|
243
301
|
if (validSingleAttrTypes.Count == 0)
|
|
244
302
|
{
|
|
@@ -250,6 +308,12 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
250
308
|
{
|
|
251
309
|
context.CancellationToken.ThrowIfCancellationRequested();
|
|
252
310
|
|
|
311
|
+
string targetInterfaceFullName = messageInfo.TargetInterfaceFullName;
|
|
312
|
+
if (targetInterfaceFullName is null)
|
|
313
|
+
{
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
253
317
|
// If nested, ensure all containers are declared partial; otherwise report diagnostic and skip
|
|
254
318
|
if (messageInfo.TypeSymbol.ContainingType is not null)
|
|
255
319
|
{
|
|
@@ -276,7 +340,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
276
340
|
);
|
|
277
341
|
foreach (INamedTypeSymbol container in nonPartial)
|
|
278
342
|
{
|
|
279
|
-
SyntaxReference
|
|
343
|
+
SyntaxReference sr =
|
|
280
344
|
container.DeclaringSyntaxReferences.FirstOrDefault();
|
|
281
345
|
if (sr != null && sr.GetSyntax() is TypeDeclarationSyntax tds)
|
|
282
346
|
{
|
|
@@ -300,7 +364,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
300
364
|
|
|
301
365
|
// Generate the partial IMessage implementation source
|
|
302
366
|
string implSource = GenerateImplementationSource(
|
|
303
|
-
|
|
367
|
+
targetInterfaceFullName,
|
|
304
368
|
messageInfo.TypeSymbol
|
|
305
369
|
);
|
|
306
370
|
string implHintName =
|
|
@@ -327,11 +391,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
327
391
|
? string.Empty
|
|
328
392
|
: $"namespace {namespaceName}\n{{";
|
|
329
393
|
string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
|
|
330
|
-
const string
|
|
394
|
+
const string Indent = " ";
|
|
331
395
|
|
|
332
396
|
// Build container wrappers so partial can merge nested types correctly
|
|
333
397
|
var containers = new Stack<INamedTypeSymbol>();
|
|
334
|
-
INamedTypeSymbol
|
|
398
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
335
399
|
while (current is not null)
|
|
336
400
|
{
|
|
337
401
|
containers.Push(current);
|
|
@@ -340,7 +404,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
340
404
|
|
|
341
405
|
var containersOpen = new StringBuilder();
|
|
342
406
|
var containersClose = new StringBuilder();
|
|
343
|
-
string currentIndent =
|
|
407
|
+
string currentIndent = Indent;
|
|
344
408
|
foreach (INamedTypeSymbol container in containers)
|
|
345
409
|
{
|
|
346
410
|
string containerAccessibility = container.DeclaredAccessibility switch
|
|
@@ -373,7 +437,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
373
437
|
$"{currentIndent}{containerAccessibility} partial {containerKind} {container.Name}{containerTypeParams}"
|
|
374
438
|
);
|
|
375
439
|
containersOpen.Append(currentIndent).AppendLine("{");
|
|
376
|
-
currentIndent +=
|
|
440
|
+
currentIndent += Indent;
|
|
377
441
|
}
|
|
378
442
|
|
|
379
443
|
string innerIndent = currentIndent;
|
|
@@ -415,7 +479,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
415
479
|
{
|
|
416
480
|
currentIndent = currentIndent.Substring(
|
|
417
481
|
0,
|
|
418
|
-
Math.Max(0, currentIndent.Length -
|
|
482
|
+
Math.Max(0, currentIndent.Length - Indent.Length)
|
|
419
483
|
);
|
|
420
484
|
containersClose.Append(currentIndent).AppendLine("}");
|
|
421
485
|
}
|
|
@@ -440,7 +504,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
440
504
|
private static List<INamedTypeSymbol> GetNonPartialContainers(INamedTypeSymbol typeSymbol)
|
|
441
505
|
{
|
|
442
506
|
List<INamedTypeSymbol> result = new();
|
|
443
|
-
INamedTypeSymbol
|
|
507
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
444
508
|
while (current is not null)
|
|
445
509
|
{
|
|
446
510
|
if (!IsDeclaredFullyPartial(current))
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
<PropertyGroup>
|
|
3
3
|
<TargetFramework>netstandard2.0</TargetFramework>
|
|
4
4
|
<LangVersion>latest</LangVersion>
|
|
5
|
+
<MicrosoftCodeAnalysisVersion>4.2.0</MicrosoftCodeAnalysisVersion>
|
|
6
|
+
<SystemCollectionsImmutableVersion>5.0.0</SystemCollectionsImmutableVersion>
|
|
7
|
+
<SystemReflectionMetadataVersion>9.0.0</SystemReflectionMetadataVersion>
|
|
8
|
+
<SystemRuntimeCompilerServicesUnsafeVersion>6.0.0</SystemRuntimeCompilerServicesUnsafeVersion>
|
|
9
|
+
<SystemTextEncodingsWebVersion>6.0.0</SystemTextEncodingsWebVersion>
|
|
5
10
|
</PropertyGroup>
|
|
6
11
|
<ItemGroup>
|
|
7
12
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
|
|
@@ -29,16 +34,31 @@
|
|
|
29
34
|
<UnityAssetsEditorWallstopStudiosDir>$([System.IO.Path]::Combine('$(UnityAssetsEditorDir)', 'Wallstop Studios'))</UnityAssetsEditorWallstopStudiosDir>
|
|
30
35
|
<UnityAssetsPluginsEditorWallstopDir>$([System.IO.Path]::Combine('$(UnityAssetsDir)', 'Plugins', 'Editor', 'WallstopStudios.DxMessaging'))</UnityAssetsPluginsEditorWallstopDir>
|
|
31
36
|
<AnalyzerDll>$(TargetPath)</AnalyzerDll>
|
|
32
|
-
<AnalyzerPdb>$(TargetDir)$(TargetName).pdb</AnalyzerPdb>
|
|
33
37
|
</PropertyGroup>
|
|
34
38
|
<ItemGroup>
|
|
35
39
|
<AnalyzerOutput Include="$(AnalyzerDll)" />
|
|
36
|
-
<
|
|
40
|
+
<AnalyzerDependency Include="$(NuGetPackageRoot)microsoft.codeanalysis.common\$(MicrosoftCodeAnalysisVersion)\lib\netstandard2.0\Microsoft.CodeAnalysis.dll" />
|
|
41
|
+
<AnalyzerDependency Include="$(NuGetPackageRoot)microsoft.codeanalysis.csharp\$(MicrosoftCodeAnalysisVersion)\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll" />
|
|
42
|
+
<AnalyzerDependency Include="$(NuGetPackageRoot)system.collections.immutable\$(SystemCollectionsImmutableVersion)\lib\netstandard2.0\System.Collections.Immutable.dll" />
|
|
43
|
+
<AnalyzerDependency
|
|
44
|
+
Include="$(NuGetPackageRoot)system.reflection.metadata\$(SystemReflectionMetadataVersion)\lib\net6.0\System.Reflection.Metadata.dll"
|
|
45
|
+
Condition="Exists('$(NuGetPackageRoot)system.reflection.metadata\$(SystemReflectionMetadataVersion)\lib\net6.0\System.Reflection.Metadata.dll')"
|
|
46
|
+
/>
|
|
47
|
+
<AnalyzerDependency
|
|
48
|
+
Include="$(NuGetPackageRoot)system.reflection.metadata\$(SystemReflectionMetadataVersion)\lib\netstandard2.0\System.Reflection.Metadata.dll"
|
|
49
|
+
Condition="Exists('$(NuGetPackageRoot)system.reflection.metadata\$(SystemReflectionMetadataVersion)\lib\netstandard2.0\System.Reflection.Metadata.dll')"
|
|
50
|
+
/>
|
|
51
|
+
<AnalyzerDependency Include="$(NuGetPackageRoot)system.runtime.compilerservices.unsafe\$(SystemRuntimeCompilerServicesUnsafeVersion)\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll" />
|
|
52
|
+
<AllAnalyzerFiles Include="@(AnalyzerOutput)" />
|
|
53
|
+
<AllAnalyzerFiles
|
|
54
|
+
Include="@(AnalyzerDependency)"
|
|
55
|
+
Condition="Exists('%(AnalyzerDependency.Identity)')"
|
|
56
|
+
/>
|
|
37
57
|
</ItemGroup>
|
|
38
58
|
<!-- Copy into package Editor/Analyzers (used by SetupCscRsp and package consumers) -->
|
|
39
59
|
<MakeDir Directories="$(EditorAnalyzersDir)" Condition="!Exists('$(EditorAnalyzersDir)')" />
|
|
40
60
|
<Copy
|
|
41
|
-
SourceFiles="@(
|
|
61
|
+
SourceFiles="@(AllAnalyzerFiles)"
|
|
42
62
|
DestinationFolder="$(EditorAnalyzersDir)"
|
|
43
63
|
SkipUnchangedFiles="true"
|
|
44
64
|
/>
|
|
@@ -49,7 +69,7 @@
|
|
|
49
69
|
Condition="Exists('$(UnityAssetsDir)') AND !Exists('$(UnityAssetsPluginsEditorWallstopDir)')"
|
|
50
70
|
/>
|
|
51
71
|
<Copy
|
|
52
|
-
SourceFiles="@(
|
|
72
|
+
SourceFiles="@(AllAnalyzerFiles)"
|
|
53
73
|
DestinationFolder="$(UnityAssetsPluginsEditorWallstopDir)"
|
|
54
74
|
SkipUnchangedFiles="true"
|
|
55
75
|
Condition="Exists('$(UnityAssetsPluginsEditorWallstopDir)')"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
using System.Collections.Generic;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using NUnit.Framework;
|
|
5
|
+
|
|
6
|
+
namespace WallstopStudios.DxMessaging.SourceGenerators.Tests;
|
|
7
|
+
|
|
8
|
+
[TestFixture]
|
|
9
|
+
public sealed class DocsSnippetCompilationTests
|
|
10
|
+
{
|
|
11
|
+
private static readonly HashSet<string> IgnoredSnippetDiagnosticIds = new(
|
|
12
|
+
StringComparer.OrdinalIgnoreCase
|
|
13
|
+
)
|
|
14
|
+
{
|
|
15
|
+
"CS0106", // modifier not valid in script (partial snippets showing members only)
|
|
16
|
+
"CS1001", // identifier expected (intentionally elided samples)
|
|
17
|
+
"CS8803", // top-level statements mixed with declarations (visual guide style snippets)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
[Test]
|
|
21
|
+
public void QuickStartStep1Compiles()
|
|
22
|
+
{
|
|
23
|
+
string docsRoot = ResolveDocsRoot();
|
|
24
|
+
string quickStartPath = Path.Combine(docsRoot, "QuickStart.md");
|
|
25
|
+
Assert.That(File.Exists(quickStartPath), Is.True, $"Unable to locate {quickStartPath}.");
|
|
26
|
+
|
|
27
|
+
string snippet = ExtractFirstCodeBlock(quickStartPath, "csharp");
|
|
28
|
+
Assert.That(!string.IsNullOrWhiteSpace(snippet), Is.True, "QuickStart snippet not found.");
|
|
29
|
+
|
|
30
|
+
string source = $"""
|
|
31
|
+
using DxMessaging.Core.Messages;
|
|
32
|
+
using DxMessaging.Core.Attributes;
|
|
33
|
+
using UnityEngine;
|
|
34
|
+
|
|
35
|
+
{snippet}
|
|
36
|
+
""";
|
|
37
|
+
|
|
38
|
+
var diagnostics = GeneratorTestUtilities
|
|
39
|
+
.CompileSnippet(source)
|
|
40
|
+
.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
|
|
41
|
+
.ToArray();
|
|
42
|
+
|
|
43
|
+
if (diagnostics.Length > 0)
|
|
44
|
+
{
|
|
45
|
+
string message = string.Join(
|
|
46
|
+
System.Environment.NewLine,
|
|
47
|
+
diagnostics.Select(d => d.ToString())
|
|
48
|
+
);
|
|
49
|
+
Assert.Fail(
|
|
50
|
+
$"QuickStart snippet failed to compile:{System.Environment.NewLine}{message}"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[TestCaseSource(nameof(GetDocumentationSnippets))]
|
|
56
|
+
public void DocumentationSnippetsCompile(string markdownPath, string snippet)
|
|
57
|
+
{
|
|
58
|
+
Assert.That(
|
|
59
|
+
snippet,
|
|
60
|
+
Is.Not.Empty,
|
|
61
|
+
$"Snippet extracted from {markdownPath} should not be empty."
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
var diagnostics = GeneratorTestUtilities
|
|
65
|
+
.ParseSnippet(snippet)
|
|
66
|
+
.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error)
|
|
67
|
+
.ToArray();
|
|
68
|
+
|
|
69
|
+
var actionableDiagnostics = diagnostics
|
|
70
|
+
.Where(d => !IgnoredSnippetDiagnosticIds.Contains(d.Id))
|
|
71
|
+
.ToArray();
|
|
72
|
+
|
|
73
|
+
if (actionableDiagnostics.Length > 0)
|
|
74
|
+
{
|
|
75
|
+
string message = string.Join(
|
|
76
|
+
System.Environment.NewLine,
|
|
77
|
+
actionableDiagnostics.Select(d => d.ToString())
|
|
78
|
+
);
|
|
79
|
+
Assert.Fail(
|
|
80
|
+
$"Documentation snippet in {markdownPath} failed to compile:{System.Environment.NewLine}{message}"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static IEnumerable<TestCaseData> GetDocumentationSnippets()
|
|
86
|
+
{
|
|
87
|
+
string docsRoot = ResolveDocsRoot();
|
|
88
|
+
foreach (
|
|
89
|
+
string markdownPath in Directory.GetFiles(docsRoot, "*.md", SearchOption.AllDirectories)
|
|
90
|
+
)
|
|
91
|
+
{
|
|
92
|
+
foreach (string snippet in ExtractCodeBlocks(markdownPath, "csharp"))
|
|
93
|
+
{
|
|
94
|
+
if (ShouldSkipSnippet(snippet))
|
|
95
|
+
{
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
yield return new TestCaseData(markdownPath, snippet).SetName(
|
|
100
|
+
$"{Path.GetFileName(markdownPath)} compiles"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private static string ExtractFirstCodeBlock(string markdownPath, string infoString)
|
|
107
|
+
{
|
|
108
|
+
return ExtractCodeBlocks(markdownPath, infoString).FirstOrDefault() ?? string.Empty;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static IEnumerable<string> ExtractCodeBlocks(string markdownPath, string infoString)
|
|
112
|
+
{
|
|
113
|
+
string[] lines = File.ReadAllLines(markdownPath);
|
|
114
|
+
bool inBlock = false;
|
|
115
|
+
System.Text.StringBuilder builder = new();
|
|
116
|
+
foreach (string rawLine in lines)
|
|
117
|
+
{
|
|
118
|
+
string line = rawLine.TrimEnd();
|
|
119
|
+
if (!inBlock)
|
|
120
|
+
{
|
|
121
|
+
if (line.StartsWith("```") && line.Length > 3 && line[3..].StartsWith(infoString))
|
|
122
|
+
{
|
|
123
|
+
inBlock = true;
|
|
124
|
+
builder.Clear();
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (line.StartsWith("```"))
|
|
130
|
+
{
|
|
131
|
+
inBlock = false;
|
|
132
|
+
string snippet = builder.ToString();
|
|
133
|
+
if (!string.IsNullOrWhiteSpace(snippet))
|
|
134
|
+
{
|
|
135
|
+
yield return snippet;
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
builder.AppendLine(rawLine);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static string ResolveDocsRoot()
|
|
145
|
+
{
|
|
146
|
+
string currentDirectoryPath = TestContext.CurrentContext.TestDirectory;
|
|
147
|
+
while (!string.IsNullOrEmpty(currentDirectoryPath))
|
|
148
|
+
{
|
|
149
|
+
string docsDirectory = Path.Combine(currentDirectoryPath, "Docs");
|
|
150
|
+
string candidate = Path.Combine(docsDirectory, "QuickStart.md");
|
|
151
|
+
if (File.Exists(candidate))
|
|
152
|
+
{
|
|
153
|
+
return docsDirectory;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
string parentDirectoryPath =
|
|
157
|
+
Path.GetDirectoryName(currentDirectoryPath) ?? string.Empty;
|
|
158
|
+
if (string.IsNullOrEmpty(parentDirectoryPath))
|
|
159
|
+
{
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
currentDirectoryPath = parentDirectoryPath;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw new FileNotFoundException(
|
|
167
|
+
"Unable to locate Docs/QuickStart.md from the current test directory."
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private static bool ShouldSkipSnippet(string snippet)
|
|
172
|
+
{
|
|
173
|
+
if (string.IsNullOrWhiteSpace(snippet))
|
|
174
|
+
{
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (snippet.Contains("..."))
|
|
179
|
+
{
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
foreach (char c in snippet)
|
|
184
|
+
{
|
|
185
|
+
if (c > 127 && !char.IsWhiteSpace(c))
|
|
186
|
+
{
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|