Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
</PropertyGroup>
<ItemGroup>
<!-- Cratis -->
<PackageVersion Include="Cratis.Screenplay.Generation.Contracts" Version="0.13.2" />
<PackageVersion Include="Cratis.Screenplay.Generation" Version="0.13.2" />
<PackageVersion Include="Cratis.Screenplay.Generation.DotNet" Version="0.13.2" />
<PackageVersion Include="Cratis.Screenplay.Generation.DotNet.Vogen" Version="0.13.2" />
<PackageVersion Include="Cratis.Screenplay.Generation.Contracts" Version="0.15.0" />
<PackageVersion Include="Cratis.Screenplay.Generation" Version="0.15.0" />
<PackageVersion Include="Cratis.Screenplay.Generation.DotNet" Version="0.15.0" />
<PackageVersion Include="Cratis.Screenplay.Generation.DotNet.Vogen" Version="0.15.0" />
<!-- Canonical fixtures -->
<PackageVersion Include="Marten" Version="9.29.0" />
<PackageVersion Include="Vogen" Version="8.0.7" />
Expand Down
8 changes: 4 additions & 4 deletions Integration/PackageConsumer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ static void AssertDependencyGraph()
using var document = JsonDocument.Parse(File.ReadAllText(dependencyFile));
var libraries = document.RootElement.GetProperty("libraries");

AssertPackage(libraries, "Cratis.Screenplay.Generation.Contracts/0.13.2");
AssertPackage(libraries, "Cratis.Screenplay.Generation/0.13.2");
AssertPackage(libraries, "Cratis.Screenplay.Generation.DotNet/0.13.2");
AssertPackage(libraries, "Cratis.Screenplay.Generation.DotNet.Vogen/0.13.2");
AssertPackage(libraries, "Cratis.Screenplay.Generation.Contracts/0.15.0");
AssertPackage(libraries, "Cratis.Screenplay.Generation/0.15.0");
AssertPackage(libraries, "Cratis.Screenplay.Generation.DotNet/0.15.0");
AssertPackage(libraries, "Cratis.Screenplay.Generation.DotNet.Vogen/0.15.0");

if (libraries.EnumerateObject().Any(_ => _.Name.StartsWith("Vogen/", StringComparison.Ordinal)))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class when_analyzing_marten_documents : given.a_marten_document_applicati
[Fact] void should_not_invent_an_event_built_read_model() => _graph.Artifacts.Any(_ => _.Key.Kind == ArtifactKind.ReadModel).ShouldBeFalse();
[Fact] void should_report_each_ordinary_document_language_gap() => Contribution.Diagnostics.Count(_ => _.Code == MartenDiagnosticCodes.DocumentModelOmitted).ShouldEqual(3);
[Fact] void should_diagnose_unresolved_and_ambiguous_identity_configurations_without_guessing() => Contribution.Diagnostics.Count(_ => _.Code == MartenDiagnosticCodes.DocumentIdentityUnresolved).ShouldEqual(2);
[Fact] void should_not_discover_a_document_from_a_generated_syntax_tree() => _graph.Artifacts.Any(_ => _.Key.Kind == ArtifactKind.Document && _.Variants.Single().Definition.Name == "GeneratedStudent").ShouldBeFalse();
[Fact] void should_not_contribute_a_fact_for_a_generated_document() => Contribution.Facts.OfType<ArtifactFact>().Any(_ => _.Definition.Name == "GeneratedStudent").ShouldBeFalse();
[Fact] void should_not_report_a_document_model_omission_for_a_generated_document() => Contribution.Diagnostics.Any(_ => _.Code == MartenDiagnosticCodes.DocumentModelOmitted && _.Message.Contains("GeneratedStudent", StringComparison.Ordinal)).ShouldBeFalse();

IReadOnlyList<Evidence> DocumentEvidence => Artifact("Student").Variants.Single().Evidence;
ArtifactDefinition Document => Artifact("Student").Variants.Single().Definition;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,15 @@ [Fact] void should_make_conventional_version_diagnostics_parameter_specific() =>
[Fact] void should_not_guess_the_receiver_alias_event() => EventNames.ShouldNotContain("AliasedEvent");
[Fact] void should_not_guess_an_event_before_an_opaque_spread() => EventNames.ShouldNotContain("OpaqueLeadingEvent");
[Fact] void should_not_guess_object_dynamic_variable_or_helper_payloads() => EventNames.Any(_ => new[] { "OpaqueObjectEvent", "DynamicEvent", "VariableEvent", "HelperEvent", "NestedContainerEvent" }.Contains(_, StringComparer.Ordinal)).ShouldBeFalse();
[Fact] void should_report_each_unresolved_exact_append_once() => Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).Count.ShouldEqual(6);
[Fact] void should_report_each_unresolved_exact_append_once() => Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).Count.ShouldEqual(9);
[Fact] void should_locate_each_unresolved_append_in_authored_source() => Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).All(_ => _.Source?.Path == "Transfers/Handlers.cs").ShouldBeTrue();
[Fact] void should_anchor_each_unresolved_append_at_its_authored_occurrence() => Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).All(_ => _.Source!.StartLine > 0 && _.Source.StartColumn > 0).ShouldBeTrue();
[Fact] void should_preserve_each_unresolved_append_occurrence_without_collapsing_locations() => Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).Select(_ => (_.Source!.StartLine, _.Source.StartColumn)).Distinct().Count().ShouldEqual(9);
[Fact] void should_report_unresolved_appends_in_authored_declaration_order() => UnresolvedAppendStartLines.SequenceEqual(UnresolvedAppendStartLines.Order()).ShouldBeTrue();
[Fact] void should_not_promote_a_member_receiver_append_to_an_event() => EventNames.ShouldNotContain("MemberReceiverEvent");
[Fact] void should_not_promote_an_object_round_trip_receiver_append_to_an_event() => EventNames.ShouldNotContain("ObjectRoundTripEvent");
[Fact] void should_not_flatten_a_nested_payload_container() => EventNames.ShouldNotContain("NestedContainerEvent");
[Fact] void should_recognize_an_event_with_a_computed_constructor_value_by_type() => EventNames.ShouldContain("OrderConfirmed");
[Fact] void should_report_each_attributed_multiple_stream_binding_once() => Diagnostics(WolverineDiagnosticCodes.MultipleStreamMetadataOmitted).Count.ShouldEqual(6);
[Fact] void should_locate_each_multiple_stream_metadata_loss() => Diagnostics(WolverineDiagnosticCodes.MultipleStreamMetadataOmitted).All(_ => _.Source?.Path == "Transfers/Handlers.cs").ShouldBeTrue();
[Fact] void should_report_version_and_concurrency_metadata_per_binding() => Diagnostics(WolverineDiagnosticCodes.StreamVersionOmitted).Count.ShouldEqual(7);
Expand Down Expand Up @@ -136,4 +143,9 @@ bool HasArtifactNamed(params string[] names) => _result.Graph.Artifacts.Any(_ =>
_.Variants.Any(variant => names.Contains(variant.Definition.Name, StringComparer.Ordinal)));

IReadOnlyList<GenerationDiagnostic> Diagnostics(string code) => [.. _result.Diagnostics.Where(_ => _.Code == code)];

IReadOnlyList<int> UnresolvedAppendStartLines =>
[
.. Diagnostics(WolverineDiagnosticCodes.EventWriteTargetUnresolved).Select(_ => _.Source!.StartLine)
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ public static void Configure(Marten.StoreOptions options)
}
""";

const string GeneratedApplicationSource =
"""
// <auto-generated/>
namespace Students;

public class GeneratedStudent
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}

public static class GeneratedStudentEndpoints
{
public static void Store(GeneratedStudent student, Marten.IDocumentSession session) => session.Store(student);
}
""";

static readonly IReadOnlyList<MetadataReference> _references =
[
.. ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
Expand All @@ -100,7 +117,8 @@ void Establish()
"Students",
[
CSharpSyntaxTree.ParseText(FrameworkSource, path: "/workspace/Framework.cs"),
CSharpSyntaxTree.ParseText(ApplicationSource, path: "/workspace/Students/Students.cs")
CSharpSyntaxTree.ParseText(ApplicationSource, path: "/workspace/Students/Students.cs"),
CSharpSyntaxTree.ParseText(GeneratedApplicationSource, path: "/workspace/Students/GeneratedStudent.g.cs")
],
_references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ public record InspectAccount(System.Guid AccountId);
public record ConventionalVersionCommand(System.Guid FromId, System.Guid ToId, long Version);
public record FalseIdentityCommand([property: Transfers.Identity] System.Guid Candidate);
public record LegacyAppend(System.Guid AccountId);
public record MemberReceiverAppend(System.Guid Id);
public record ObjectRoundTripAppend(System.Guid Id);
public record ObjectPayloadAppend(System.Guid Id);

public class Account
{
Expand Down Expand Up @@ -165,6 +168,8 @@ public record SagaSiblingAppended(System.Guid Id);
public record RouteFollowUp(System.Guid Id);
public record RouteAppended(System.Guid Id);
public record LegacyAppended(System.Guid Id);
public record MemberReceiverEvent(System.Guid Id);
public record ObjectRoundTripEvent(System.Guid Id);

public sealed class BoundaryResponse : Wolverine.IResponseAware;
public sealed class BoundaryEffect : Wolverine.ISideEffect;
Expand All @@ -176,6 +181,11 @@ public partial class GeneratedBaseSaga;

public interface IAccountEventStream : JasperFx.Events.IEventStream<Account>;

public sealed class MemberReceiverStream
{
public JasperFx.Events.IEventStream<Account> Inner { get; set; } = null!;
}

public sealed class UnrelatedStream<T>
{
public void AppendOne(object @event) { }
Expand Down Expand Up @@ -403,6 +413,27 @@ public static class SagaMixedAppendHandler
public static void Handle(SagaMixedAppend command, JasperFx.Events.IEventStream<Account> stream) =>
stream.AppendMany(new TransferSaga(), new SagaSiblingAppended(command.Id));
}

public static class MemberReceiverAppendHandler
{
public static void Handle(MemberReceiverAppend command, MemberReceiverStream stream) =>
stream.Inner.AppendOne(new MemberReceiverEvent(command.Id));
}

public static class ObjectRoundTripAppendHandler
{
public static void Handle(ObjectRoundTripAppend command, JasperFx.Events.IEventStream<Account> stream)
{
object boxed = stream;
((JasperFx.Events.IEventStream<Account>)boxed).AppendOne(new ObjectRoundTripEvent(command.Id));
}
}

public static class ObjectPayloadAppendHandler
{
public static void Handle(ObjectPayloadAppend command, JasperFx.Events.IEventStream<Account> stream) =>
stream.AppendOne(new object());
}
""";

const string GeneratedApplicationSource =
Expand Down
4 changes: 2 additions & 2 deletions Source/DotNET/CritterStack/Marten/MartenDocumentFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public static MartenDiscoveryResult Discover(
foreach (var tree in project.AuthoredSyntaxTrees.Where(_ => !DotNetGeneratedSource.IsGenerated(_)))
{
var semanticModel = project.Compilation.GetSemanticModel(tree);
foreach (var invocation in tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>())
foreach (var invocation in DotNetSource.AuthoredInvocationsIn(tree.GetRoot(), project))
{
if (semanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol method || !IsMarten(method))
if (DotNetInvocations.MethodFor(invocation, semanticModel) is not { } method || !IsMarten(method))
{
continue;
}
Expand Down
30 changes: 18 additions & 12 deletions Source/DotNET/CritterStack/Wolverine/WolverineEventStreams.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ public static WolverineEventStreamAppendDiscovery Appends(

foreach (var (declaration, semanticModel) in WolverineMethodSyntax.Declarations(method, project))
{
foreach (var invocationSyntax in declaration.DescendantNodes().OfType<InvocationExpressionSyntax>())
foreach (var invocationSyntax in DotNetSource.AuthoredInvocationsIn(declaration, project))
{
if (!IsInDirectHandlerBody(invocationSyntax, declaration) ||
DotNetInvocations.MethodFor(invocationSyntax, semanticModel) is not { } invocationMethod ||
semanticModel.GetOperation(invocationSyntax) is not IInvocationOperation invocation ||
!TryGetAppendModel(invocation, project, out var invokedModel))
{
Expand All @@ -135,7 +136,9 @@ public static WolverineEventStreamAppendDiscovery Appends(

hasDirectWrite = true;
var source = CritterStackSource.RangeForProject(invocationSyntax.GetLocation(), project);
if (ReceiverParameter(invocation.Instance) is not { } receiver ||
if (DotNetInvocations.ReceiverFor(invocationSyntax, invocationMethod, semanticModel) is not { } receiverExpression ||
(semanticModel.GetOperation(receiverExpression) ?? invocation.Instance) is not { } receiverOperation ||
ReceiverParameter(receiverOperation) is not { } receiver ||
bindings.FirstOrDefault(binding =>
SymbolEqualityComparer.Default.Equals(binding.Parameter, receiver) &&
SymbolEqualityComparer.Default.Equals(binding.ModelType, invokedModel)) is not { } binding)
Expand Down Expand Up @@ -205,20 +208,13 @@ static bool TryGetAppendModel(
}

if (!SymbolEqualityComparer.Default.Equals(method.ContainingType.OriginalDefinition, streamDefinition) ||
method.ContainingType.TypeArguments.SingleOrDefault() is not INamedTypeSymbol model ||
!streamDefinition.GetMembers(method.Name).OfType<IMethodSymbol>().Any(candidate =>
SymbolEqualityComparer.Default.Equals(candidate.OriginalDefinition, method.OriginalDefinition)) ||
method.Parameters.Length != 1)
method.ContainingType.TypeArguments.SingleOrDefault() is not INamedTypeSymbol model)
{
return false;
}

var isExactAppend = method.Name switch
{
"AppendOne" => method.Parameters[0].Type.SpecialType == SpecialType.System_Object,
"AppendMany" => IsObjectArray(method.Parameters[0].Type) || IsObjectEnumerable(method.Parameters[0].Type),
_ => false
};
var isExactAppend = ExpectedAppendSignatures(streamDefinition)
.Any(signature => DotNetMethodSignatures.Matches(method, signature));
if (isExactAppend)
{
modelType = model;
Expand All @@ -227,6 +223,16 @@ static bool TryGetAppendModel(
return isExactAppend;
}

static IEnumerable<DotNetMethodSignature> ExpectedAppendSignatures(INamedTypeSymbol streamDefinition) =>
streamDefinition.GetMembers("AppendOne")
.OfType<IMethodSymbol>()
.Where(candidate => candidate.Parameters is [{ Type.SpecialType: SpecialType.System_Object }])
.Concat(streamDefinition.GetMembers("AppendMany")
.OfType<IMethodSymbol>()
.Where(candidate => candidate.Parameters is [var parameter] &&
(IsObjectArray(parameter.Type) || IsObjectEnumerable(parameter.Type))))
.Select(DotNetMethodSignatures.From);

static IReadOnlyList<INamedTypeSymbol> EventStreamInterfaces(ITypeSymbol type)
{
if (type is not INamedTypeSymbol named)
Expand Down
5 changes: 3 additions & 2 deletions Source/DotNET/CritterStack/Wolverine/WolverineMethodSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ static class WolverineMethodSyntax
IMethodSymbol method,
DotNetProjectCompilation project)
{
var authoredDeclarations = DotNetSource.AuthoredDeclarationsOf(method, project.AuthoredSyntaxTrees).ToHashSet();
foreach (var syntaxReference in method.DeclaringSyntaxReferences)
{
if (syntaxReference.GetSyntax() is MethodDeclarationSyntax declaration &&
project.AuthoredSyntaxTrees.Contains(declaration.SyntaxTree) &&
if (authoredDeclarations.Contains(syntaxReference) &&
syntaxReference.GetSyntax() is MethodDeclarationSyntax declaration &&
!DotNetGeneratedSource.IsGenerated(declaration.SyntaxTree))
{
yield return (declaration, project.Compilation.GetSemanticModel(declaration.SyntaxTree));
Expand Down
Loading