Skip to content

Commit 16515e1

Browse files
committed
fixup: address PR #264 review feedback
- CHANGELOG [Unreleased] Changed: document mdlQuote/unquoteString escape- sequence behaviour change (now treats \n \r \t \\ as escapes instead of literal pairs). Compatibility break flagged for users who intentionally embedded raw backslash-letter sequences in MDL string literals. - Add comment in expressionToString noting the dual use of mdlQuote for MDL source output vs Mendix expression strings, and the describe→re-execute trade-off it implies. - Extract defaultLogNodeExpression constant ('Application') so the builder, the formatter, and cmd_diff_mdl share a single source of truth for the LOG node fallback. - Add mdl-examples/bug-tests/264-log-node-expression-roundtrip.mdl reproducer per CLAUDE.md checklist.
1 parent 9e538ce commit 16515e1

7 files changed

Lines changed: 67 additions & 4 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1313
- **Path normalization** — Relative paths in `MetadataUrl` are automatically converted to absolute `file://` URLs for Studio Pro compatibility
1414
- **ServiceUrl validation**`ServiceUrl` parameter must now be a constant reference (e.g., `@Module.ConstantName`) to enforce Mendix best practice
1515
- **Shared URL utilities**`internal/pathutil` package with `NormalizeURL()`, `URIToPath()`, and `PathFromURL()` for reuse across components
16+
17+
### Changed
18+
19+
- **MDL string literal escapes**`mdlQuote`/`unquoteString` now treat `\n`, `\r`, `\t`, and `\\` inside single-quoted literals as escape sequences (previously a literal backslash followed by the letter). This is a compatibility break for any MDL script that intentionally embedded a raw `\n` / `\t` / `\\` as two characters; such scripts must now double the backslash (`\\n` to preserve the two-character form). Applies to `LOG` messages, `@caption`/`@annotation` text, and other string literals round-tripped via the describer.
20+
1621
## [0.7.0] - 2026-04-21
1722

1823
### Added
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
-- ============================================================================
2+
-- Bug #264: Log node expressions and microflow describe roundtrip
3+
-- ============================================================================
4+
--
5+
-- Three related regressions surfaced together while exercising real .mpr files:
6+
--
7+
-- 1. Legacy `NewCaseValue` on sequence flows (Mx 9) was being dropped by the
8+
-- parser, so re-describing a Mx 9 project silently lost decision labels.
9+
-- 2. The describer bailed or emitted malformed MDL when it hit partially
10+
-- understood activity shapes — a single unknown subfield broke the entire
11+
-- microflow roundtrip.
12+
-- 3. The MDL grammar didn't accept the `log` activity's expression-typed NODE
13+
-- parameter, so any microflow containing a log node with a variable or
14+
-- constant reference failed to re-parse after describe.
15+
--
16+
-- Usage:
17+
-- mxcli exec mdl-examples/bug-tests/264-log-node-expression-roundtrip.mdl -p app.mpr
18+
-- Then: mxcli describe microflow BugTest264.MF_LogWithNode -p app.mpr
19+
-- The output must re-execute cleanly against the same project.
20+
-- ============================================================================
21+
22+
create module BugTest264;
23+
24+
-- String-literal node (backwards-compatible shape)
25+
create microflow BugTest264.MF_LogWithLiteralNode ()
26+
begin
27+
log info node 'BugTest264' 'started';
28+
end;
29+
/
30+
31+
-- Variable-ref node — exercises Node expression path
32+
create microflow BugTest264.MF_LogWithNode (
33+
$nodeName: string
34+
)
35+
begin
36+
log info node $nodeName 'hello';
37+
end;
38+
/
39+
40+
-- Multi-line message with embedded newline — exercises the mdlQuote escape
41+
-- round-trip (`\n` is decoded on parse, re-encoded on describe).
42+
create microflow BugTest264.MF_LogMultilineMessage ()
43+
begin
44+
log info node 'BugTest264' 'line 1\nline 2\nline 3';
45+
end;
46+
/

mdl/executor/cmd_diff_mdl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func microflowStatementToMDL(ctx *ExecContext, stmt ast.MicroflowStatement, inde
345345
lines = append(lines, indentStr+"end loop;")
346346

347347
case *ast.LogStmt:
348-
nodeStr := "'Application'"
348+
nodeStr := defaultLogNodeExpression
349349
if s.Node != nil {
350350
nodeStr = diffExpressionToString(ctx, s.Node)
351351
}

mdl/executor/cmd_microflows_builder_calls.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
"github.com/mendixlabs/mxcli/sdk/microflows"
1515
)
1616

17+
// defaultLogNodeExpression is the quoted Mendix expression used for the log
18+
// node when none is specified on a LOG statement. Single source of truth shared
19+
// by the builder, the formatter, and cmd_diff_mdl.
20+
const defaultLogNodeExpression = "'Application'"
21+
1722
// addLogMessageAction creates a LOG statement as a LogMessageAction.
1823
func (fb *flowBuilder) addLogMessageAction(s *ast.LogStmt) model.ID {
1924
logLevel := microflows.LogLevelInfo
@@ -65,7 +70,7 @@ func (fb *flowBuilder) addLogMessageAction(s *ast.LogStmt) model.ID {
6570
templateParams = []string{fb.exprToString(s.Message)}
6671
}
6772

68-
logNodeName := "'Application'"
73+
logNodeName := defaultLogNodeExpression
6974
if s.Node != nil {
7075
logNodeName = fb.exprToString(s.Node)
7176
}

mdl/executor/cmd_microflows_format_action.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ func formatAction(
360360
// Output it as-is since it's already stored as an expression
361361
node := a.LogNodeName
362362
if node == "" {
363-
node = "'Application'" // Default value as a string literal expression
363+
node = defaultLogNodeExpression
364364
}
365365
message := "'Message'"
366366
if a.MessageTemplate != nil && len(a.MessageTemplate.Translations) > 0 {

mdl/executor/cmd_microflows_helpers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ func mendixFunctionName(name string) string {
166166
}
167167

168168
// expressionToString converts an AST Expression to a Mendix expression string.
169+
// Note: string literals are quoted via mdlQuote, which escapes backslashes,
170+
// newlines, tabs, and carriage returns for MDL round-trip safety. Mendix's
171+
// expression engine does not treat `\n` etc. as escapes, so a string literal
172+
// with an embedded raw newline round-trips as `\n` in the MDL source (parseable)
173+
// but is re-serialised into BSON as a two-character `\n` sequence rather than a
174+
// real newline. This is the correct trade-off for describe→re-execute flows;
175+
// the alternative (emitting raw control chars in MDL) would break the parser.
169176
func expressionToString(expr ast.Expression) string {
170177
// Check for nil interface
171178
if expr == nil {

mdl/executor/roundtrip_microflow_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func TestRoundtripMicroflow_LogWithNodeExpression(t *testing.T) {
148148
t.Fatalf("Failed to describe microflow: %v", err)
149149
}
150150

151-
if !containsProperty(output, "LOG INFO NODE @"+testModule+".SecurityLogNode") {
151+
if !containsProperty(output, "log info node @"+testModule+".SecurityLogNode") {
152152
t.Fatalf("Expected constant node expression in output, got:\n%s", output)
153153
}
154154

0 commit comments

Comments
 (0)