From 769b7fb150d24e24d461cc60b677cffed8586bad Mon Sep 17 00:00:00 2001 From: Henrique Costa Date: Sat, 2 May 2026 18:04:55 +0200 Subject: [PATCH] fix: parse negative annotation coordinates Symptom: `@position(-150, -210)` parsed without errors, but the stored coordinates became `(0, 0)`. Re-executing described microflows then moved activities whose canvas coordinates were negative. Root cause: annotation parameters allow expressions, so a leading minus is parsed as a unary expression rather than a `NUMBER_LITERAL`. The annotation integer helper only read direct numeric literals and silently fell back to zero for unary-negative values. Fix: parse the raw annotation parameter text as an integer before falling back to the literal-only path, preserving both positive and negative integer coordinates without evaluating arbitrary expressions. Tests: added a synthetic microflow visitor test for negative `@position` coordinates; `make build`; `make lint-go`; `make test`. --- mdl/visitor/visitor_entity.go | 3 +++ mdl/visitor/visitor_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/mdl/visitor/visitor_entity.go b/mdl/visitor/visitor_entity.go index 39ecae23..3069243b 100644 --- a/mdl/visitor/visitor_entity.go +++ b/mdl/visitor/visitor_entity.go @@ -497,6 +497,9 @@ func parseAnnotationParamInt(ctx parser.IAnnotationParamContext) int { } paramCtx := ctx.(*parser.AnnotationParamContext) if valueCtx := paramCtx.AnnotationValue(); valueCtx != nil { + if val, err := strconv.Atoi(strings.TrimSpace(valueCtx.GetText())); err == nil { + return val + } annValue := valueCtx.(*parser.AnnotationValueContext) if lit := annValue.Literal(); lit != nil { litCtx := lit.(*parser.LiteralContext) diff --git a/mdl/visitor/visitor_test.go b/mdl/visitor/visitor_test.go index 74d9b720..f808b926 100644 --- a/mdl/visitor/visitor_test.go +++ b/mdl/visitor/visitor_test.go @@ -1804,6 +1804,31 @@ END;` } } +func TestMicroflowPositionAnnotationAcceptsNegativeCoordinates(t *testing.T) { + input := `CREATE MICROFLOW Synthetic.Check () +BEGIN + @position(-150, -210) + LOG INFO NODE 'SyntheticLog' 'message'; +END;` + + prog, errs := Build(input) + if len(errs) > 0 { + t.Fatalf("unexpected parse errors: %v", errs) + } + + stmt := prog.Statements[0].(*ast.CreateMicroflowStmt) + logStmt, ok := stmt.Body[0].(*ast.LogStmt) + if !ok { + t.Fatalf("Expected LogStmt, got %T", stmt.Body[0]) + } + if logStmt.Annotations == nil || logStmt.Annotations.Position == nil { + t.Fatal("expected position annotation") + } + if got := *logStmt.Annotations.Position; got.X != -150 || got.Y != -210 { + t.Fatalf("position = (%d, %d), want (-150, -210)", got.X, got.Y) + } +} + func TestCallJavaActionAcceptsEmptyArguments(t *testing.T) { input := `CREATE MICROFLOW Synthetic.Check () RETURNS Boolean AS $Success