diff --git a/mdl/executor/cmd_microflows_builder_calls.go b/mdl/executor/cmd_microflows_builder_calls.go index c5a7f666..cdc2c061 100644 --- a/mdl/executor/cmd_microflows_builder_calls.go +++ b/mdl/executor/cmd_microflows_builder_calls.go @@ -241,15 +241,32 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. } } - // Build a map of parameter name -> param type for the Java action + // Build a map of parameter name -> param type for the Java action. + // resolvedBasicParams tracks parameters whose type was successfully + // resolved via the Java action definition AND is not an entity-type or + // microflow-type parameter (i.e. anything that lands in + // BasicCodeActionParameterValue: String, Integer, Boolean, ListType, + // ParameterizedEntityType, etc.). When the MDL author binds such a + // parameter to `empty`, Studio Pro authors `Argument: "empty"` (the MDL + // literal string) rather than `Argument: ""`, the unbound marker. The + // distinction matters: `mx check` reports CE0126 "Missing value for + // parameter X" when a typed parameter receives the unbound `""` shape. + // + // Without a backend lookup (jaDef == nil) we fall back to the prior + // `""` behaviour to preserve the documented "intentionally unbound" + // semantics of PROPOSAL_microflow_empty_java_action_argument.md. entityTypeParams := make(map[string]bool) microflowTypeParams := make(map[string]bool) + resolvedBasicParams := make(map[string]bool) if jaDef != nil { for _, p := range jaDef.Parameters { - if _, ok := p.ParameterType.(*javaactions.EntityTypeParameterType); ok { + switch p.ParameterType.(type) { + case *javaactions.EntityTypeParameterType: entityTypeParams[p.Name] = true - } else if _, ok := p.ParameterType.(*javaactions.MicroflowType); ok { + case *javaactions.MicroflowType: microflowTypeParams[p.Name] = true + default: + resolvedBasicParams[p.Name] = true } } } @@ -285,9 +302,23 @@ func (fb *flowBuilder) addCallJavaActionAction(s *ast.CallJavaActionStmt) model. Microflow: "", } } else { + // When the Java action definition is available and the + // parameter is a typed BasicParameterType (anything that + // isn't entity-type or microflow-type — String, Integer, + // Boolean, ListType, ParameterizedEntityType, etc.), Studio + // Pro authors `Argument: "empty"` for the MDL `empty` + // literal. Without that information (jaDef == nil) keep the + // blank-string "intentionally unbound" marker that + // PROPOSAL_microflow_empty_java_action_argument.md + // established for code-action callers without backend + // resolution. + argument := "" + if resolvedBasicParams[arg.Name] { + argument = "empty" + } value = µflows.BasicCodeActionParameterValue{ BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())}, - Argument: "", + Argument: argument, } } } else { diff --git a/mdl/executor/cmd_microflows_builder_java_action_test.go b/mdl/executor/cmd_microflows_builder_java_action_test.go index aabcddbd..1206f68d 100644 --- a/mdl/executor/cmd_microflows_builder_java_action_test.go +++ b/mdl/executor/cmd_microflows_builder_java_action_test.go @@ -61,6 +61,73 @@ func TestBuildJavaAction_EmptyArgumentPreservesEmptyBasicValue(t *testing.T) { } } +// TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword pins +// the BSON shape Studio Pro authors when a typed (non-entity-type, +// non-microflow-type) Java action parameter is bound to MDL `empty`: +// the BasicCodeActionParameterValue.Argument holds the literal string +// "empty". Emitting the blank `""` for such a parameter triggers +// `mx check` CE0126 "Missing value for parameter X" because the model +// treats the parameter as missing rather than explicitly empty. The +// behaviour applies regardless of the inner type (String, ListType, +// ParameterizedEntityType, …) — the discriminator is whether the +// backend resolved the parameter at all. +func TestBuildJavaAction_EmptyResolvedBasicArgumentEmitsEmptyKeyword(t *testing.T) { + cases := []struct { + name string + paramType javaactions.CodeActionParameterType + }{ + {"list", &javaactions.ListType{Entity: "SampleModule.Tag"}}, + {"string", &javaactions.StringType{}}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + fb := &flowBuilder{ + posX: 100, + posY: 100, + spacing: HorizontalSpacing, + backend: &mock.MockBackend{ + ReadJavaActionByNameFunc: func(qualifiedName string) (*javaactions.JavaAction, error) { + if qualifiedName != "SampleModule.AddBatch" { + t.Fatalf("java action lookup = %q", qualifiedName) + } + return &javaactions.JavaAction{ + Parameters: []*javaactions.JavaActionParameter{ + {Name: "Param", ParameterType: tc.paramType}, + }, + }, nil + }, + }, + } + stmt := &ast.CallJavaActionStmt{ + ActionName: ast.QualifiedName{Module: "SampleModule", Name: "AddBatch"}, + Arguments: []ast.CallArgument{ + {Name: "Param", Value: &ast.LiteralExpr{Kind: ast.LiteralEmpty}}, + }, + } + + id := fb.addCallJavaActionAction(stmt) + var activity *microflows.ActionActivity + for _, obj := range fb.objects { + if obj.GetID() == id { + activity, _ = obj.(*microflows.ActionActivity) + break + } + } + if activity == nil { + t.Fatal("expected Java action activity") + } + action := activity.Action.(*microflows.JavaActionCallAction) + value, ok := action.ParameterMappings[0].Value.(*microflows.BasicCodeActionParameterValue) + if !ok { + t.Fatalf("mapping value = %T, want *BasicCodeActionParameterValue", action.ParameterMappings[0].Value) + } + if value.Argument != "empty" { + t.Fatalf("resolved empty argument = %q, want %q", value.Argument, "empty") + } + }) + } +} + func TestBuildJavaAction_EmptyMicroflowArgumentUsesMicroflowParameterValue(t *testing.T) { fb := &flowBuilder{ posX: 100,