com.wallstop-studios.dxmessaging 2.1.0 → 2.1.2
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 +1 -0
- package/Docs/Comparisons.md +4 -4
- package/Docs/EmitShorthands.md +2 -2
- package/Docs/Helpers.md +90 -75
- package/Docs/Install.md +2 -1
- package/Docs/Patterns.md +1 -1
- package/Docs/Performance.md +13 -11
- package/Docs/QuickStart.md +1 -2
- 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 +21 -0
- 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 +9 -3
- package/Runtime/AssemblyInfo.cs +1 -0
- package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs +1 -1
- package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs +1 -1
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +1 -1
- package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs +1 -1
- package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs +1 -1
- package/Runtime/Core/Diagnostics/MessageEmissionData.cs +26 -11
- package/Runtime/Core/Extensions/MessageBusExtensions.cs +2 -2
- package/Runtime/Core/Extensions/MessageExtensions.cs +2 -2
- package/Runtime/Core/InstanceId.cs +5 -3
- package/Runtime/Core/MessageBus/MessageBus.cs +4 -4
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +2 -2
- package/Runtime/Core/MessageBus/MessagingRegistration.cs +3 -3
- package/Runtime/Core/MessageHandler.cs +34 -2
- package/Runtime/Core/MessageRegistrationToken.cs +2 -2
- package/Runtime/Core/Messages/IBroadcastMessage.cs +1 -1
- package/Runtime/Core/Messages/ITargetedMessage.cs +1 -1
- package/Runtime/Core/Messages/IUntargetedMessage.cs +1 -1
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +2 -0
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +2 -0
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +2 -0
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +2 -0
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +2 -0
- package/Runtime/Unity/MessageAwareComponent.cs +2 -0
- package/Runtime/Unity/MessageBusProviderHandle.cs +4 -0
- package/Runtime/Unity/MessagingComponent.cs +27 -7
- package/Runtime/Unity/MessagingComponentInstaller.cs +2 -0
- package/Runtime/Unity/ScriptableMessageBusProvider.cs +2 -0
- package/Samples~/DI/Reflex/SampleInstaller.cs +6 -6
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs +7 -9
- package/Samples~/DI/Zenject/SampleInstaller.cs +6 -8
- package/SourceGenerators/Directory.Build.props +9 -0
- package/{package-lock.json.meta → SourceGenerators/Directory.Build.props.meta} +2 -2
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +19 -24
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +87 -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 +3 -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/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.csproj +20 -7
- package/package.json +1 -1
package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs
CHANGED
|
@@ -208,7 +208,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
208
208
|
// Location-specific suggestions on each non-partial container
|
|
209
209
|
foreach (INamedTypeSymbol container in nonPartial)
|
|
210
210
|
{
|
|
211
|
-
SyntaxReference
|
|
211
|
+
SyntaxReference sr =
|
|
212
212
|
container.DeclaringSyntaxReferences.FirstOrDefault();
|
|
213
213
|
if (sr != null && sr.GetSyntax() is TypeDeclarationSyntax tds)
|
|
214
214
|
{
|
|
@@ -258,11 +258,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
258
258
|
? string.Empty
|
|
259
259
|
: $"namespace {namespaceName}\n{{";
|
|
260
260
|
string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
|
|
261
|
-
const string
|
|
261
|
+
const string Indent = " ";
|
|
262
262
|
|
|
263
263
|
// Build container wrappers for nested types so the partial can merge correctly
|
|
264
264
|
var containers = new Stack<INamedTypeSymbol>();
|
|
265
|
-
INamedTypeSymbol
|
|
265
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
266
266
|
while (current is not null)
|
|
267
267
|
{
|
|
268
268
|
containers.Push(current);
|
|
@@ -271,7 +271,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
271
271
|
|
|
272
272
|
var containersOpen = new StringBuilder();
|
|
273
273
|
var containersClose = new StringBuilder();
|
|
274
|
-
string currentIndent =
|
|
274
|
+
string currentIndent = Indent; // one level inside namespace (or top-level)
|
|
275
275
|
|
|
276
276
|
foreach (INamedTypeSymbol container in containers)
|
|
277
277
|
{
|
|
@@ -306,7 +306,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
306
306
|
$"{currentIndent}{containerAccessibility} partial {containerKind} {container.Name}{containerTypeParams}"
|
|
307
307
|
);
|
|
308
308
|
containersOpen.Append(currentIndent).AppendLine("{");
|
|
309
|
-
currentIndent +=
|
|
309
|
+
currentIndent += Indent;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
string innerIndent = currentIndent; // indent level for the target (innermost) type
|
|
@@ -350,13 +350,8 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
350
350
|
SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
|
|
351
351
|
);
|
|
352
352
|
|
|
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)>();
|
|
353
|
+
List<(string Type, string Name, bool IsOptional, string DefaultExpr)> parameterDetails =
|
|
354
|
+
new List<(string Type, string Name, bool IsOptional, string DefaultExpr)>();
|
|
360
355
|
|
|
361
356
|
// For validating expressions, use the semantic model for this type's tree
|
|
362
357
|
SemanticModel semanticModel = compilation.GetSemanticModel(
|
|
@@ -368,7 +363,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
368
363
|
{
|
|
369
364
|
string fieldType = field.Type.ToDisplayString(fieldTypeFormat);
|
|
370
365
|
string fieldName = field.Name;
|
|
371
|
-
string
|
|
366
|
+
string defaultExpr = null;
|
|
372
367
|
bool isOptional = false;
|
|
373
368
|
|
|
374
369
|
foreach (AttributeData attr in field.GetAttributes())
|
|
@@ -455,10 +450,10 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
455
450
|
}
|
|
456
451
|
else if (arg.Kind == TypedConstantKind.Primitive)
|
|
457
452
|
{
|
|
458
|
-
object
|
|
453
|
+
object val = arg.Value;
|
|
459
454
|
defaultExpr = FormatLiteral(val, arg.Type);
|
|
460
455
|
// Validate primitive conversion to field type
|
|
461
|
-
ITypeSymbol
|
|
456
|
+
ITypeSymbol sourceType = arg.Type;
|
|
462
457
|
if (sourceType != null)
|
|
463
458
|
{
|
|
464
459
|
Conversion conv = compilation.ClassifyConversion(
|
|
@@ -489,12 +484,12 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
489
484
|
}
|
|
490
485
|
|
|
491
486
|
parameterDetails.Add((fieldType, fieldName, isOptional, defaultExpr));
|
|
492
|
-
constructorBody.AppendLine($"{
|
|
487
|
+
constructorBody.AppendLine($"{Indent}{Indent} this.{fieldName} = {fieldName};");
|
|
493
488
|
}
|
|
494
489
|
|
|
495
490
|
for (int i = 0; i < parameterDetails.Count; i++)
|
|
496
491
|
{
|
|
497
|
-
(string Type, string Name, bool IsOptional, string
|
|
492
|
+
(string Type, string Name, bool IsOptional, string DefaultExpr) p =
|
|
498
493
|
parameterDetails[i];
|
|
499
494
|
constructorParams.Append($"{p.Type} {p.Name}");
|
|
500
495
|
if (p.IsOptional)
|
|
@@ -520,7 +515,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
520
515
|
{
|
|
521
516
|
currentIndent = currentIndent.Substring(
|
|
522
517
|
0,
|
|
523
|
-
Math.Max(0, currentIndent.Length -
|
|
518
|
+
Math.Max(0, currentIndent.Length - Indent.Length)
|
|
524
519
|
);
|
|
525
520
|
containersClose.Append(currentIndent).AppendLine("}");
|
|
526
521
|
}
|
|
@@ -533,9 +528,9 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
533
528
|
{{namespaceBlockOpen}}
|
|
534
529
|
{{containersOpen}}{{innerIndent}}{{typeAccessibility}} partial {{typeKind}} {{typeName}}
|
|
535
530
|
{{innerIndent}}{
|
|
536
|
-
{{
|
|
537
|
-
{{
|
|
538
|
-
{{
|
|
531
|
+
{{Indent}} /// <summary>
|
|
532
|
+
{{Indent}} /// Auto-generated constructor by DxAutoGenConstructorGenerator.
|
|
533
|
+
{{Indent}} /// </summary>
|
|
539
534
|
{{innerIndent}} {{constructorAccessibility}} {{typeSymbol.Name}}({{constructorParams}})
|
|
540
535
|
{{innerIndent}} {
|
|
541
536
|
{{constructorBody}}
|
|
@@ -546,7 +541,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
546
541
|
""";
|
|
547
542
|
}
|
|
548
543
|
|
|
549
|
-
private static string FormatLiteral(object
|
|
544
|
+
private static string FormatLiteral(object value, ITypeSymbol type)
|
|
550
545
|
{
|
|
551
546
|
if (value == null)
|
|
552
547
|
{
|
|
@@ -635,7 +630,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
635
630
|
SpeculativeBindingOption.BindAsExpression
|
|
636
631
|
);
|
|
637
632
|
|
|
638
|
-
ITypeSymbol
|
|
633
|
+
ITypeSymbol sourceType = typeInfo.Type;
|
|
639
634
|
if (sourceType == null)
|
|
640
635
|
{
|
|
641
636
|
// Could not bind; let the compiler decide but report as invalid here
|
|
@@ -655,7 +650,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
655
650
|
private static List<INamedTypeSymbol> GetNonPartialContainers(INamedTypeSymbol typeSymbol)
|
|
656
651
|
{
|
|
657
652
|
List<INamedTypeSymbol> result = new();
|
|
658
|
-
INamedTypeSymbol
|
|
653
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
659
654
|
while (current is not null)
|
|
660
655
|
{
|
|
661
656
|
if (!IsDeclaredFullyPartial(current))
|
package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs
CHANGED
|
@@ -66,7 +66,8 @@ 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
|
|
|
72
73
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
@@ -143,15 +144,15 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
143
144
|
return null; // Cannot be a concrete message type
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
string
|
|
147
|
+
string foundTargetInterface = null;
|
|
147
148
|
bool multipleAttributes = false;
|
|
148
149
|
|
|
149
150
|
// Check attributes to find the specific message type (Broadcast, Targeted, etc.)
|
|
150
151
|
foreach (AttributeData attributeData in typeSymbol.GetAttributes())
|
|
151
152
|
{
|
|
152
153
|
cancellationToken.ThrowIfCancellationRequested();
|
|
153
|
-
string
|
|
154
|
-
string
|
|
154
|
+
string currentAttributeFullName = attributeData.AttributeClass?.ToDisplayString();
|
|
155
|
+
string targetInterfaceForThisAttribute = null;
|
|
155
156
|
|
|
156
157
|
switch (currentAttributeFullName)
|
|
157
158
|
{
|
|
@@ -180,17 +181,21 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
if (multipleAttributes
|
|
184
|
+
if (multipleAttributes)
|
|
185
|
+
{
|
|
186
|
+
foundTargetInterface = null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (foundTargetInterface == null && !multipleAttributes)
|
|
184
190
|
{
|
|
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
191
|
return null;
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
return new MessageToGenerateInfo(
|
|
191
195
|
typeSymbol,
|
|
192
196
|
typeDeclarationSyntax,
|
|
193
|
-
foundTargetInterface
|
|
197
|
+
foundTargetInterface,
|
|
198
|
+
multipleAttributes
|
|
194
199
|
);
|
|
195
200
|
}
|
|
196
201
|
|
|
@@ -206,18 +211,19 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
// --- Step 1: Filter out types with multiple attributes applied ---
|
|
209
|
-
Dictionary<ISymbol, MessageToGenerateInfo> uniqueTypes = new
|
|
214
|
+
Dictionary<ISymbol, MessageToGenerateInfo> uniqueTypes = new Dictionary<
|
|
215
|
+
ISymbol,
|
|
216
|
+
MessageToGenerateInfo
|
|
217
|
+
>(SymbolEqualityComparer.Default);
|
|
218
|
+
HashSet<ISymbol> conflictingTypes = new HashSet<ISymbol>(
|
|
210
219
|
SymbolEqualityComparer.Default
|
|
211
220
|
);
|
|
212
|
-
HashSet<ISymbol> typesWithMultipleAttributes = new(SymbolEqualityComparer.Default);
|
|
213
221
|
|
|
214
222
|
foreach (MessageToGenerateInfo typeInfo in typesToGenerate)
|
|
215
223
|
{
|
|
216
|
-
if (
|
|
224
|
+
if (typeInfo.HasConflictingMessageAttributes)
|
|
217
225
|
{
|
|
218
|
-
|
|
219
|
-
// This implies multiple different valid attributes were found, report error.
|
|
220
|
-
if (typesWithMultipleAttributes.Add(typeInfo.TypeSymbol)) // Report only once
|
|
226
|
+
if (conflictingTypes.Add(typeInfo.TypeSymbol))
|
|
221
227
|
{
|
|
222
228
|
context.ReportDiagnostic(
|
|
223
229
|
Diagnostic.Create(
|
|
@@ -226,7 +232,44 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
226
232
|
typeInfo.TypeSymbol.ToDisplayString()
|
|
227
233
|
)
|
|
228
234
|
);
|
|
229
|
-
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (conflictingTypes.Contains(typeInfo.TypeSymbol))
|
|
241
|
+
{
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (typeInfo.TargetInterfaceFullName is null)
|
|
246
|
+
{
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
uniqueTypes.TryGetValue(
|
|
252
|
+
typeInfo.TypeSymbol,
|
|
253
|
+
out MessageToGenerateInfo existingInfo
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
{
|
|
257
|
+
if (
|
|
258
|
+
!string.Equals(
|
|
259
|
+
existingInfo.TargetInterfaceFullName,
|
|
260
|
+
typeInfo.TargetInterfaceFullName,
|
|
261
|
+
StringComparison.Ordinal
|
|
262
|
+
) && conflictingTypes.Add(typeInfo.TypeSymbol)
|
|
263
|
+
)
|
|
264
|
+
{
|
|
265
|
+
context.ReportDiagnostic(
|
|
266
|
+
Diagnostic.Create(
|
|
267
|
+
MultipleAttributesError,
|
|
268
|
+
typeInfo.DeclarationSyntax.Identifier.GetLocation(),
|
|
269
|
+
typeInfo.TypeSymbol.ToDisplayString()
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
uniqueTypes.Remove(typeInfo.TypeSymbol);
|
|
230
273
|
}
|
|
231
274
|
}
|
|
232
275
|
else
|
|
@@ -235,10 +278,21 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
235
278
|
}
|
|
236
279
|
}
|
|
237
280
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
281
|
+
if (uniqueTypes.Count == 0)
|
|
282
|
+
{
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
List<MessageToGenerateInfo> validSingleAttrTypes = new List<MessageToGenerateInfo>();
|
|
287
|
+
foreach (KeyValuePair<ISymbol, MessageToGenerateInfo> entry in uniqueTypes)
|
|
288
|
+
{
|
|
289
|
+
if (conflictingTypes.Contains(entry.Key))
|
|
290
|
+
{
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
validSingleAttrTypes.Add(entry.Value);
|
|
295
|
+
}
|
|
242
296
|
|
|
243
297
|
if (validSingleAttrTypes.Count == 0)
|
|
244
298
|
{
|
|
@@ -250,6 +304,12 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
250
304
|
{
|
|
251
305
|
context.CancellationToken.ThrowIfCancellationRequested();
|
|
252
306
|
|
|
307
|
+
string targetInterfaceFullName = messageInfo.TargetInterfaceFullName;
|
|
308
|
+
if (targetInterfaceFullName is null)
|
|
309
|
+
{
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
253
313
|
// If nested, ensure all containers are declared partial; otherwise report diagnostic and skip
|
|
254
314
|
if (messageInfo.TypeSymbol.ContainingType is not null)
|
|
255
315
|
{
|
|
@@ -276,7 +336,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
276
336
|
);
|
|
277
337
|
foreach (INamedTypeSymbol container in nonPartial)
|
|
278
338
|
{
|
|
279
|
-
SyntaxReference
|
|
339
|
+
SyntaxReference sr =
|
|
280
340
|
container.DeclaringSyntaxReferences.FirstOrDefault();
|
|
281
341
|
if (sr != null && sr.GetSyntax() is TypeDeclarationSyntax tds)
|
|
282
342
|
{
|
|
@@ -300,7 +360,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
300
360
|
|
|
301
361
|
// Generate the partial IMessage implementation source
|
|
302
362
|
string implSource = GenerateImplementationSource(
|
|
303
|
-
|
|
363
|
+
targetInterfaceFullName,
|
|
304
364
|
messageInfo.TypeSymbol
|
|
305
365
|
);
|
|
306
366
|
string implHintName =
|
|
@@ -327,11 +387,11 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
327
387
|
? string.Empty
|
|
328
388
|
: $"namespace {namespaceName}\n{{";
|
|
329
389
|
string namespaceBlockClose = string.IsNullOrEmpty(namespaceName) ? string.Empty : "}";
|
|
330
|
-
const string
|
|
390
|
+
const string Indent = " ";
|
|
331
391
|
|
|
332
392
|
// Build container wrappers so partial can merge nested types correctly
|
|
333
393
|
var containers = new Stack<INamedTypeSymbol>();
|
|
334
|
-
INamedTypeSymbol
|
|
394
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
335
395
|
while (current is not null)
|
|
336
396
|
{
|
|
337
397
|
containers.Push(current);
|
|
@@ -340,7 +400,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
340
400
|
|
|
341
401
|
var containersOpen = new StringBuilder();
|
|
342
402
|
var containersClose = new StringBuilder();
|
|
343
|
-
string currentIndent =
|
|
403
|
+
string currentIndent = Indent;
|
|
344
404
|
foreach (INamedTypeSymbol container in containers)
|
|
345
405
|
{
|
|
346
406
|
string containerAccessibility = container.DeclaredAccessibility switch
|
|
@@ -373,7 +433,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
373
433
|
$"{currentIndent}{containerAccessibility} partial {containerKind} {container.Name}{containerTypeParams}"
|
|
374
434
|
);
|
|
375
435
|
containersOpen.Append(currentIndent).AppendLine("{");
|
|
376
|
-
currentIndent +=
|
|
436
|
+
currentIndent += Indent;
|
|
377
437
|
}
|
|
378
438
|
|
|
379
439
|
string innerIndent = currentIndent;
|
|
@@ -415,7 +475,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
415
475
|
{
|
|
416
476
|
currentIndent = currentIndent.Substring(
|
|
417
477
|
0,
|
|
418
|
-
Math.Max(0, currentIndent.Length -
|
|
478
|
+
Math.Max(0, currentIndent.Length - Indent.Length)
|
|
419
479
|
);
|
|
420
480
|
containersClose.Append(currentIndent).AppendLine("}");
|
|
421
481
|
}
|
|
@@ -440,7 +500,7 @@ namespace WallstopStudios.DxMessaging.SourceGenerators
|
|
|
440
500
|
private static List<INamedTypeSymbol> GetNonPartialContainers(INamedTypeSymbol typeSymbol)
|
|
441
501
|
{
|
|
442
502
|
List<INamedTypeSymbol> result = new();
|
|
443
|
-
INamedTypeSymbol
|
|
503
|
+
INamedTypeSymbol current = typeSymbol.ContainingType;
|
|
444
504
|
while (current is not null)
|
|
445
505
|
{
|
|
446
506
|
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
|
+
}
|