From be1ac91beba901bc50cb2fb250b546d7acbff83c Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Tue, 14 Jul 2026 06:26:42 +0200 Subject: [PATCH] Scan source context before JSON property resolution Property placeholders on JSON re-export now qualify through applyResolution with CodebaseContext so ambiguous enum names fail closed instead of guessing without bindings. Co-authored-by: Cursor --- .../service/ExportService.java | 4 +- .../JsonRoundTripCanonicalizationTest.java | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java index c6fafb4..bae9356 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/service/ExportService.java @@ -167,8 +167,6 @@ public class ExportService { JsonImportService jsonImportService = new JsonImportService(); AnalysisResult result = jsonImportService.importAnalysisResult(jsonFile); - resolveProperties(result, activeProfiles, null); - CodebaseContext context = null; Path sourceRoot = optionalSourceDir; if (sourceRoot == null) { @@ -187,6 +185,8 @@ public class ExportService { log.info("JSON re-export: no source project found; using embedded machine types if present"); } + resolveProperties(result, activeProfiles, context); + StateMachineTypeResolver.MachineTypes machineTypes = click.kamil.springstatemachineexporter.analysis.service.JsonExportContextFactory.resolveMachineTypes( result.getName(), diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JsonRoundTripCanonicalizationTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JsonRoundTripCanonicalizationTest.java index b61184f..d775827 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JsonRoundTripCanonicalizationTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/JsonRoundTripCanonicalizationTest.java @@ -98,6 +98,55 @@ class JsonRoundTripCanonicalizationTest { .isEqualTo("com.example.order.OrderEvent.PAY"); } + @Test + void shouldResolvePropertyPlaceholdersOnJsonReExportWithSourceContext(@TempDir Path tempDir) throws Exception { + writeSampleProject(tempDir); + + TriggerPoint trigger = TriggerPoint.builder() + .event("${app.event}") + .sourceState("${app.state}") + .className("com.example.web.OrderController") + .methodName("pay") + .sourceFile("OrderController.java") + .polymorphicEvents(List.of("${app.event}")) + .eventTypeFqn("com.example.order.OrderEvent") + .stateTypeFqn("com.example.order.OrderState") + .build(); + + AnalysisResult shortForm = AnalysisResult.builder() + .name("com.example.config.OrderStateMachineConfiguration") + .stateTypeFqn("com.example.order.OrderState") + .eventTypeFqn("com.example.order.OrderEvent") + .transitions(List.of(shortTransition( + "OrderEvent.PAY", + "OrderState.NEW", + "OrderState.PAID"))) + .metadata(CodebaseMetadata.builder() + .triggers(List.of(trigger)) + .properties(Map.of("default", Map.of( + "app.event", "OrderEvent.PAY", + "app.state", "OrderState.NEW"))) + .build()) + .build(); + + Path jsonFile = tempDir.resolve("machine.json"); + Files.writeString(jsonFile, new JsonExporter().export(shortForm, ExportOptions.builder().build())); + + Path outputDir = tempDir.resolve("out"); + ExportService exportService = new ExportService(List.of(new JsonExporter())); + exportService.runJsonExporter(jsonFile, outputDir, List.of("json")); + + AnalysisResult roundTripped = new JsonImportService().importAnalysisResult( + outputDir.resolve("com.example.config.OrderStateMachineConfiguration") + .resolve("com.example.config.OrderStateMachineConfiguration.json")); + + TriggerPoint resolvedTrigger = roundTripped.getMetadata().getTriggers().get(0); + assertThat(resolvedTrigger.getEvent()).isEqualTo("com.example.order.OrderEvent.PAY"); + assertThat(resolvedTrigger.getSourceState()).isEqualTo("com.example.order.OrderState.NEW"); + assertThat(resolvedTrigger.getPolymorphicEvents()) + .containsExactly("com.example.order.OrderEvent.PAY"); + } + @Test void shouldRelinkMatchedTransitionsAfterPreCanonicalizingShortFormJson(@TempDir Path tempDir) throws Exception { writeSampleProject(tempDir);