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
14 changes: 8 additions & 6 deletions mdl/executor/cmd_microflows_show_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -724,18 +724,20 @@ func traverseFlow(
traverseFlowUntilMerge(ctx, trueFlow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visited, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
}

// Emit the ELSE branch only if it has statements. When the false
// flow jumps straight to the merge (the MDL was `if X then ... end if`
// with no else), emitting `else` with no body produces an empty
// branch that normalizes away on re-parse.
falseHasBody := falseFlow != nil && falseFlow.DestinationID != mergeID
if falseHasBody {
if falseFlow != nil {
elseLineIdx := len(*lines)
*lines = append(*lines, indentStr+"else")
visitedFalseBranch := make(map[model.ID]bool)
for id := range visited {
visitedFalseBranch[id] = true
}
traverseFlowUntilMerge(ctx, falseFlow.DestinationID, mergeID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, visitedFalseBranch, entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
// Remove empty else block. A false branch can point at a
// continuation already emitted through the true branch, so checking
// only falseFlow.DestinationID != mergeID is not enough.
if len(*lines) == elseLineIdx+1 {
*lines = (*lines)[:elseLineIdx]
}
}

*lines = append(*lines, indentStr+"end if;")
Expand Down
57 changes: 57 additions & 0 deletions mdl/executor/cmd_microflows_traverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,63 @@ func TestTraverseFlow_SequentialIfWithoutElseKeepsContinuationOutsideFirstIf(t *
}
}

func TestTraverseFlow_TopLevelIfRemovesEmptyElseAfterVisitedContinuation(t *testing.T) {
e := newTestExecutor()

logAction := func(id, message string) *microflows.ActionActivity {
return &microflows.ActionActivity{
BaseActivity: microflows.BaseActivity{BaseMicroflowObject: mkObj(id)},
Action: &microflows.LogMessageAction{
LogLevel: "Info",
LogNodeName: "'Synthetic'",
MessageTemplate: &model.Text{Translations: map[string]string{"en_US": message}},
},
}
}

activityMap := map[model.ID]microflows.MicroflowObject{
mkID("start"): &microflows.StartEvent{BaseMicroflowObject: mkObj("start")},
mkID("outer_split"): &microflows.ExclusiveSplit{
BaseMicroflowObject: mkObj("outer_split"),
SplitCondition: &microflows.ExpressionSplitCondition{Expression: "$UseNestedPath"},
},
mkID("inner_split"): &microflows.ExclusiveSplit{
BaseMicroflowObject: mkObj("inner_split"),
SplitCondition: &microflows.ExpressionSplitCondition{Expression: "$HasContinuation"},
},
mkID("inner_return"): &microflows.EndEvent{BaseMicroflowObject: mkObj("inner_return")},
mkID("tail_log"): logAction("tail_log", "shared tail"),
mkID("end"): &microflows.EndEvent{BaseMicroflowObject: mkObj("end")},
}
flowsByOrigin := map[model.ID][]*microflows.SequenceFlow{
mkID("start"): {mkFlow("start", "outer_split")},
mkID("outer_split"): {
mkBranchFlow("outer_split", "inner_split", &microflows.ExpressionCase{Expression: "true"}),
mkBranchFlow("outer_split", "tail_log", &microflows.ExpressionCase{Expression: "false"}),
},
mkID("inner_split"): {
mkBranchFlow("inner_split", "tail_log", &microflows.ExpressionCase{Expression: "true"}),
mkBranchFlow("inner_split", "inner_return", &microflows.ExpressionCase{Expression: "false"}),
},
mkID("tail_log"): {mkFlow("tail_log", "end")},
}

splitMergeMap := map[model.ID]model.ID{
mkID("outer_split"): mkID("end"),
mkID("inner_split"): mkID("tail_log"),
}
var lines []string
e.traverseFlow(mkID("start"), activityMap, flowsByOrigin, splitMergeMap, make(map[model.ID]bool), nil, nil, &lines, 0, nil, 0, nil)

out := strings.Join(lines, "\n")
if strings.Contains(out, "\nelse\nend if;") {
t.Fatalf("empty top-level else should be removed:\n%s", out)
}
if got := strings.Count(out, "shared tail"); got != 1 {
t.Fatalf("shared tail emitted %d times, want once:\n%s", got, out)
}
}

func TestTraverseFlow_GuardBranchWithMultipleActivitiesKeepsContinuationOutsideElse(t *testing.T) {
e := newTestExecutor()

Expand Down
Loading