From 26efaa5557b91b547cfe444afa3c0b6fa31914be Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 05:26:08 +0000 Subject: [PATCH] fix(check): refuse a button passing its own data container by name (MDL-BUTTON02) A button directly inside data view `dvGate` passing `$dvGate` passed `check --references` and `exec`, then `mx check` reported `[CE0117] "Error(s) in expression." at Action button 'btnOwn'`. A data container's widget-name variable is in scope only for the containers nested below it; in its own context the object is $currentObject. The button-context walk now carries the nearest data container's name, moving with the same rule as MDL-BUTTON01 (a control bar takes its context from above its grid), and flags an action argument that reads it. The flagged set matches mxbuild 11.14.0's CE0117s on a ten-button probe: own data view (direct, through a container, from a control bar inside it, as `$dv/Attr`), and a list view, grid or gallery read by its own name from its item or row. The enclosing name from a nested container and a grid's selection from its own control bar build clean and are not flagged. Fixes mendixlabs/mxcli#1324 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QLPboEFUCLXZr6qwX2T9LF --- .../fix-issue/findings/mdl-executor.jsonl | 1 + .../skills/mendix/cheatsheet-errors/SKILL.md | 1 + .../mendix/create-page/reference/widgets.md | 9 ++ CHANGELOG.md | 1 + .../1324-own-data-container-name.fail.mdl | 33 +++++ mdl/executor/validate_page_button_context.go | 87 +++++++++++-- .../validate_page_own_container_name_test.go | 118 ++++++++++++++++++ 7 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl create mode 100644 mdl/executor/validate_page_own_container_name_test.go diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index 78100e9361..118867fa4b 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -868,3 +868,4 @@ {"area": "mdl/executor", "date": "2026-10-05", "symptom": "On Mendix 11.15, `TestMxCheck_DoctypeScripts/40-message-definition-examples.mdl` fails with CE0270 \"No root element could be found in the schema\" at MsgTest.IMM_Order. On a converted or Studio Pro 11.15 project, `list message definition collections` finds nothing, and `describe import mapping` prints no source", "cause": "Mendix 11.15 replaced `MessageDefinitions$MessageDefinitionCollection` with one `MessageDefinitions$MessageDefinition2` document per definition. `mx convert` turns the collection into a `Projects$Folder` with the same unit ID and name, and the ExposedEntity tree is byte-identical apart from $IDs. mxcli read and wrote only collections, so on 11.15 there was nothing to resolve a mapping against", "file": "`mdl/backend/modelsdk/messagedefinition_document.go` (read/write, reusing the collection's exposedNodeFromGen/messageNodeToGen), `mdl/executor/cmd_messagedefinition_documents.go` (create/describe/drop/list + version refusals), `mdl/executor/mapping_messagedefinition.go` (`findMessageDefinition(ctx, ref)` returns the stored reference), `cmd_messagedefinitions.go` (alter target resolution, association-drop guard), grammar `createMessageDefinitionStatement` / `DROP|DESCRIBE MESSAGE DEFINITION` / `LIST MESSAGE DEFINITIONS`", "insight": "**`mx convert` is the oracle for a storage change.** Converting a known 11.14 project gives a Studio Pro-authored 11.15 reference for the new shape and for how references move, so no guessing is needed. **Under the revert, 11.15 fails earlier than CE0270.** A two-part name written into the old `MessageDefinition` key makes mxbuild refuse to load the project (`StorageLoadException: ... is not a valid OldMessageDefinitionIdentifier`). The old key is typed as a three-part identifier even though 11.15 no longer reads it. The doctype script uses `-- @version: ..11.14` / `11.15+` sections, and the 11.15 mapping needs a different name from the older section's, because a plain `check` sees both sections and reports MDL-DUPDEF", "refs": ["ako/mxcli#987"]} {"area": "mdl/executor", "date": "2026-10-06", "symptom": "v0.25.0: `describe microflow` 4-8x slower than v0.24.0 on a flow arranged by hand in Studio Pro, and the cost grows faster than the flow (60 if/else blocks: 4.7 s -> 35.6 s); flows laid out by mxcli barely change", "cause": "The canonical describe's layout derivation (#748) re-renders the description every round, and a hand-laid flow takes gatedLayoutRounds+2 = 8 rounds. Each render re-ran the stored flow's split/merge analysis (findSplitMergePoints, labelCrossedMerges) and the body warnings (postDominators via microflowgraph.Analyze) - superlinear, and independent of which annotations the round keeps", "file": "`mdl/executor/cmd_microflows_derived_layout.go` (flowAnalysisMemo, installed by useDerivedFlowLayout, rebuildOnly view for the rounds), `mdl/executor/cmd_microflows_show.go` (findSplitMergePoints / labels / microflowBodyWarnings go through it)", "insight": "**Profile before believing the issue's theory.** The report blamed the round count, but rounds were already capped at 8; the rebuild itself was cheap. The CPU profile showed the cost was the *render* inside each round - the stored flow's graph analysis, redone per round. Memoising it per describe (keyed on the stored collection pointer) and leaving the `-- WARNING` comment lines out of rounds (the rebuild never reads them) gave 14.2 s -> 1.7 s at 60 blocks, with the printed description byte-identical to the unfixed build. A wall-clock assertion would be flaky; the test counts split/merge analyses per describe (18 over 8 rounds before, 2 after), with the round count as its control", "refs": ["mendixlabs/mxcli#1301"]} {"area":"mdl-executor","date":"2026-10-05","symptom":"A view entity selecting a **non-localized** DateTime column (Studio Pro's \"Localize\" unticked — the normal choice for a calendar date) passes `mxcli check --references` and exec, then `mx check` fails CE6770 \"View Entity is out of sync with the OQL Query.\" The view attribute was always written LocalizeDate = true, and MDL has no spelling for the flag, so describe -> exec re-broke it on every run","cause":"execCreateViewEntity built every view attribute with convertDataType, whose DateTime is Mendix's default LocalizeDate = true; nothing looked at the attribute the column reads","file":"`mdl/executor/oql_view_localize_date.go` (viewDateTimeLocalize), `mdl/executor/cmd_entities.go` (execCreateViewEntity); test `mdl/executor/view_entity_localize_date_test.go`; bug-test `mdl-examples/bug-tests/1297-view-entity-non-localized-datetime.mdl`","insight":"**Derive, don't add syntax**: on a view entity the column's localization is a property of the query, so the fix reads it from the source attribute and describe -> exec round-trips (`Unchanged`) with nothing new to spell — same reasoning as the view association (the column is the declaration). **Measure the shapes before choosing the scope**: one project, one view per shape, mxbuild 11.12.5 — pass-through, `s/Attr`, MIN, MAX and CASE over a non-localized source are ALL CE6770 when written localized and 0 errors when patched to false, so a pass-through-only rule (the obvious one, mirroring the string-length rule) would have fixed the report and left MAX(date) broken. Sources that disagree (coalesce of a localized and a non-localized column) are unmeasured and keep the default. **Repro needs a Studio Pro-authored flag**: mxcli writes every persistent DateTime localized, so the bug is invisible from MDL alone — the reporter's pymongo patch flipping Sale.SaleDate is the cheapest stand-in. Control: stubbing the assignment fails 5 of 6 cases with `LocalizeDate = true`; pre-fix binary on the same project gives 5 × CE6770, fixed gives 0","refs":["mendixlabs/mxcli#1297"]} +{"area": "mdl-executor", "date": "2026-10-07", "refs": ["mendixlabs/mxcli#1324"], "symptom": "A button directly inside data view `dvGate` passing `$dvGate` (its own container, by widget name) passes `check --references` and `exec`, then `mx check` reports `[error] [CE0117] \"Error(s) in expression.\" at Action button 'btnOwn'`. The same `$dvGate` from a button in a NESTED data view builds clean", "cause": "No check modelled which data-container names are in scope. A container's widget-name variable exists only for the data containers nested below it; in its own context the object is $currentObject", "file": "`mdl/executor/validate_page_button_context.go` (`checkButtonContextTree` carries `nearest`; `checkOwnContainerName`, `exprReadsVariable`, MDL-BUTTON02)", "insight": "**Measure the neighbours before writing the rule — the report's shape is one of six.** One probe page with ten buttons through `mx check` 11.14.0 settled the whole rule in one build: own data view, own data view through a plain container, a control bar of a grid inside it, `$dv/Attr`, a list view's own name from its item, a grid's own name from its column and a gallery's from its template are all CE0117; the enclosing name from a grid column or list item is clean, and so is a grid's own name from its control bar (the selection). So the rule is 'the NEAREST data container's name', and it moves exactly like the existing MDL-BUTTON01 context (a control bar takes its context from above its grid) — which is why it went into the same walk rather than a new one. A data-view-only fix, the literal reading of the issue, would have missed four of the six. **Unmeasured, deliberately not flagged:** the same name in a nested widget's DATA SOURCE arguments and in widget expressions (visibility, dynamic text). Control: stubbing the call fails all three tests with `got []`; end to end the fixed binary refuses the issue's script ('Nothing was written') and the suggested `$currentObject` rewrite builds at 0 errors. Repro `mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl`; tests `validate_page_own_container_name_test.go`"} diff --git a/.claude/skills/mendix/cheatsheet-errors/SKILL.md b/.claude/skills/mendix/cheatsheet-errors/SKILL.md index 578f4695f8..002af66d67 100644 --- a/.claude/skills/mendix/cheatsheet-errors/SKILL.md +++ b/.claude/skills/mendix/cheatsheet-errors/SKILL.md @@ -265,6 +265,7 @@ Run with `-p` for the fullest coverage. | CE0104 | Action activity is unreachable | Code after RETURN | | CE0105 | Must end with end event | Missing RETURN | | CE0117 | Error in expression | Unqualified association path | +| CE0117 | …on a button inside a data container | The button passes the container it sits in by its widget name (`$dvGate` inside `dvGate`). That name is a variable only for containers nested below it; use `$currentObject` there. MDL-BUTTON02 | | CE1571 | No argument selected for parameter | A microflow/nanoflow call with a parameter nothing fills — as a `datasource:` **or** an `action:`. Give it an argument (`action: call nanoflow M.NF(P = $value)`), or nest the widget in a data container of the parameter's type. `check -p` reports both | | CE1571 | …in a control bar | A control bar is **not** row-scoped, so the grid's row does not fill it: pass the grid's selection (`$dgOrders`, with `Selection:` set) or move the widget into a column. `$currentObject` there is MDL-BUTTON01 | | CE1834 | The 'Page' property is required | Workflow user task without a `page` — `check` flags MDL-WF01 | diff --git a/.claude/skills/mendix/create-page/reference/widgets.md b/.claude/skills/mendix/create-page/reference/widgets.md index e6e1154cc9..24d650928d 100644 --- a/.claude/skills/mendix/create-page/reference/widgets.md +++ b/.claude/skills/mendix/create-page/reference/widgets.md @@ -1068,6 +1068,15 @@ A **container** takes an argument list exactly like an `actionbutton` does — t two share one action grammar. Reaching for a button because a container "cannot pass parameters" changes the rendering for no reason (mendixlabs/mxcli#1082). +**A data container's name is a variable only *below* it.** `$dvOrder` reads data +view `dvOrder`'s object from a data view, list or grid nested inside it. In the +container's own context — directly inside it, through plain containers, or in the +control bar of a grid inside it — the object is `$currentObject`, and +`$dvOrder` is **CE0117** "Error(s) in expression." (`mxcli check` reports +MDL-BUTTON02). The same goes for a list view, gallery or grid read by its own +name from its item or row. A grid's own name *is* valid from its control bar — +that is the selection, above (mendixlabs/mxcli#1324). + ### Charts (Charts.mpk — ColumnChart / BarChart / AreaChart / PieChart) Charts are pluggable widgets whose data lives in one or more `series` object-list diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a7e53879..25c476784f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed +- **`check` refuses a button that passes its own data container by widget name** (mendixlabs/mxcli#1324) — `actionbutton btnOwn (Action: call microflow M.F(Gate = $dvGate))` directly inside data view `dvGate` passed `check --references` and `exec`, then `mx check` reported `[CE0117] "Error(s) in expression." at Action button 'btnOwn'`. A data container's name is a variable only for the containers nested below it; in its own context the object is `$currentObject`. Reported as **MDL-BUTTON02** (error, so `exec` refuses it), for data views, list views, galleries and data grids alike, including attribute paths (`$dvGate/Name`) and a control bar inside the container. A grid's own name from its control bar (the selection) and an enclosing container's name from a nested one are not flagged. The flagged set matches mxbuild 11.14.0's CE0117s widget for widget on a ten-button probe page. - **`run --local --watch` starts on Mendix 10.24 and 11.6** — it waited the full web-client timeout (5 minutes) for a bundle that had finished in seconds, then failed with `web client watcher timed out`. The rollup runner those versions ship reports its status as `{"code":"SUCCESS"}` rather than the modern-web-bundler protocol mxcli was reading; both are now understood, including that runner's error reports. - **`run --local --watch` no longer loses a change made while the app boots** — the watch loop took its baseline after the boot, so a model written during the ~15 s boot was never built and the app kept serving the model from before it. The baseline is now the source time the boot build was made from, so that change is built on the first tick. diff --git a/mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl b/mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl new file mode 100644 index 0000000000..eefd5f6326 --- /dev/null +++ b/mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl @@ -0,0 +1,33 @@ +-- mendixlabs/mxcli#1324: a button passing its OWN data container by widget name +-- +-- NEGATIVE TEST (.fail.mdl) — EXPECTED to fail `mxcli check`. +-- `make check-mdl` inverts the exit code: an unexpected pass is a regression of +-- the MDL-BUTTON02 rule. +-- +-- Symptom: `check --references` and `exec` were clean, then `mx check` reported +-- [error] [CE0117] "Error(s) in expression." at Action button 'btnOwn' +-- A data container's widget name ($dvGate) is a variable only for the data +-- containers nested below it. Directly inside dvGate (a plain container is +-- transparent) the object is $currentObject. The same holds for a list view, +-- gallery or data grid read by its own name from its item or row, and for a +-- control bar, which takes its context from above its grid. +-- +-- Fix: `mxcli check` flags it as MDL-BUTTON02 (error), so exec refuses it. +-- btnCur and btnOuter build clean (measured, Mendix 11.14.0) and are not flagged. + +create module G52; +create persistent entity G52.Gate (Name: String(50)); +create microflow G52.ACT_Open ($Gate: G52.Gate) +begin + log info node 'G52' 'open'; +end; + +create page G52.GatePage (Title: 'Gate', Layout: Atlas_Core.PopupLayout, Params: ( $Gate: G52.Gate )) { + dataview dvGate (DataSource: $Gate) { + actionbutton btnOwn (Caption: 'Own data view name', Action: call microflow G52.ACT_Open(Gate = $dvGate)) + actionbutton btnCur (Caption: 'Current object', Action: call microflow G52.ACT_Open(Gate = $currentObject)) + dataview dvInner (DataSource: $Gate) { + actionbutton btnOuter (Caption: 'Enclosing data view name', Action: call microflow G52.ACT_Open(Gate = $dvGate)) + } + } +}; diff --git a/mdl/executor/validate_page_button_context.go b/mdl/executor/validate_page_button_context.go index fe9071b091..d8ea51bc60 100644 --- a/mdl/executor/validate_page_button_context.go +++ b/mdl/executor/validate_page_button_context.go @@ -25,7 +25,7 @@ func ValidatePageButtonContext(prog *ast.Program) []linter.Violation { var out []linter.Violation for _, stmt := range prog.Statements { if label, widgets, ok := documentWidgets(stmt); ok { - out = append(out, checkButtonContextTree(widgets, "", false, label)...) + out = append(out, checkButtonContextTree(widgets, "", false, "", label)...) } } return out @@ -46,29 +46,102 @@ func ValidatePageButtonContext(prog *ast.Program) []linter.Violation { // context, and its control bar's $currentObject is that object — mxbuild builds // it clean. The grid's OWN data source never scopes its control bar, so the // control bar inherits the context from above the grid, not from the grid. -func checkButtonContextTree(widgets []*ast.WidgetV3, controlBarOf string, inContext bool, locationPrefix string) []linter.Violation { +// +// nearest is the name of the data container whose object the widget sits in +// ("" at the top), for MDL-BUTTON02 (mendixlabs/mxcli#1324). It moves with the +// same rule as inContext: a control bar keeps the context from above its grid. +func checkButtonContextTree(widgets []*ast.WidgetV3, controlBarOf string, inContext bool, nearest, locationPrefix string) []linter.Violation { var out []linter.Violation for _, w := range widgets { if w == nil { continue } - if controlBarOf != "" && !inContext { - if a := w.GetAction(); a != nil { + if a := w.GetAction(); a != nil { + if controlBarOf != "" && !inContext { out = append(out, checkControlBarAction(a, w.Name, controlBarOf, locationPrefix)...) } + if nearest != "" { + out = append(out, checkOwnContainerName(a, w.Name, nearest, locationPrefix)...) + } } childContext := inContext || isObjectContextContainer(w) + childNearest := nearest + if isObjectContextContainer(w) && w.Name != "" { + childNearest = w.Name + } for _, c := range w.Children { - childOf, ctx := controlBarOf, childContext + childOf, ctx, near := controlBarOf, childContext, childNearest if c != nil && strings.EqualFold(c.Type, "controlbar") { - childOf, ctx = w.Name, inContext + childOf, ctx, near = w.Name, inContext, nearest } - out = append(out, checkButtonContextTree([]*ast.WidgetV3{c}, childOf, ctx, locationPrefix)...) + out = append(out, checkButtonContextTree([]*ast.WidgetV3{c}, childOf, ctx, near, locationPrefix)...) } } return out } +// checkOwnContainerName flags an action argument (and its chained THEN action) +// that reads the nearest data container by its widget name — `$dvGate` from a +// button directly inside dvGate. A container's name is a variable only for the +// containers nested BELOW it; in its own context the object is +// $currentObject, and mxbuild reports the name as CE0117 "Error(s) in +// expression." (mendixlabs/mxcli#1324, measured on 11.14.0 for data views, +// list views, galleries and data grids alike). +func checkOwnContainerName(a *ast.ActionV3, widgetName, nearest, locationPrefix string) []linter.Violation { + var out []linter.Violation + for a != nil { + for _, arg := range a.Args { + s, ok := arg.Value.(string) + if !ok || !exprReadsVariable(s, nearest) { + continue + } + out = append(out, linter.Violation{ + RuleID: "MDL-BUTTON02", + Severity: linter.SeverityError, + Message: fmt.Sprintf( + "%s: `%s` passes `%s` to its %s action, but `%s` is the data container the widget sits in directly — its name is a variable only inside a data container nested below it (CE0117)", + locationPrefix, widgetName, s, a.Type, nearest), + Suggestion: fmt.Sprintf( + "Use $currentObject for the object of `%s` here (`$currentObject/Attr` for an attribute); `$%s` works from a data view, list or grid nested inside it.", + nearest, nearest), + }) + } + a = a.ThenAction + } + return out +} + +// exprReadsVariable reports whether expression source reads $name as a +// variable: a `$name` token outside a string literal, ending at a +// non-identifier character. Case-insensitive, as widget names are unique +// case-insensitively on a page. +func exprReadsVariable(expr, name string) bool { + inString := false + for i := 0; i < len(expr); i++ { + c := expr[i] + if c == '\'' { + inString = !inString // '' inside a literal toggles twice + continue + } + if inString || c != '$' { + continue + } + j := i + 1 + for j < len(expr) && isExprIdentByte(expr[j]) { + j++ + } + if strings.EqualFold(expr[i+1:j], name) { + return true + } + i = j - 1 + } + return false +} + +func isExprIdentByte(c byte) bool { + return c == '_' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' +} + // isObjectContextContainer reports whether w gives its (non-control-bar) // children a current object: a data view's object, a list view or gallery // item, a grid row (column content). diff --git a/mdl/executor/validate_page_own_container_name_test.go b/mdl/executor/validate_page_own_container_name_test.go new file mode 100644 index 0000000000..d2f0629570 --- /dev/null +++ b/mdl/executor/validate_page_own_container_name_test.go @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 + +package executor + +import ( + "sort" + "strings" + "testing" + + "github.com/mendixlabs/mxcli/mdl/visitor" +) + +// ownContainerFlagged returns the widgets MDL-BUTTON02 names, sorted. +func ownContainerFlagged(t *testing.T, src string) []string { + t.Helper() + prog, errs := visitor.Build(src) + if len(errs) > 0 { + t.Fatalf("parse errors: %v", errs) + } + var names []string + for _, v := range ValidatePageButtonContext(prog) { + if v.RuleID != "MDL-BUTTON02" { + continue + } + start := strings.Index(v.Message, "`") + end := strings.Index(v.Message[start+1:], "`") + names = append(names, v.Message[start+1:start+1+end]) + } + sort.Strings(names) + return names +} + +// mendixlabs/mxcli#1324: a data container's widget-name variable ($dvGate) is +// only in scope for widgets nested in ANOTHER data container below it. From the +// container's own direct context it is not a variable at all, and mxbuild +// 11.14.0 reports `[CE0117] "Error(s) in expression." at Action button 'btnOwn'` +// while check and exec were clean. This is the issue's reproduction verbatim. +func TestValidatePageButtonContext_OwnDataViewName_Issue1324(t *testing.T) { + src := `create page "G52"."GatePage" (Title: 'Gate', Layout: Atlas_Core.PopupLayout, Params: { $Gate: "G52"."Gate" }) { + dataview dvGate (DataSource: $Gate) { + actionbutton btnOwn (Caption: 'Own data view name', Action: microflow "G52"."ACT_Open"(Gate: $dvGate)) + actionbutton btnCur (Caption: 'Current object', Action: microflow "G52"."ACT_Open"(Gate: $currentObject)) + dataview dvInner (DataSource: $Gate) { + actionbutton btnOuter (Caption: 'Enclosing data view name', Action: microflow "G52"."ACT_Open"(Gate: $dvGate)) + } + } +};` + got := ownContainerFlagged(t, src) + if strings.Join(got, ",") != "btnOwn" { + t.Fatalf("want only btnOwn flagged (btnCur and btnOuter build clean), got %v", got) + } +} + +// Every verdict below was measured with `mx check` on Mendix 11.14.0: the +// flagged set is exactly the set mxbuild reported CE0117 for. The nearest +// data container decides — a plain container is transparent, a control bar +// takes the context from ABOVE its grid (so a grid's own selection stays +// spellable there), and a row or item of a nested list is a new context. +func TestValidatePageButtonContext_OwnContainerName_MeasuredShapes(t *testing.T) { + src := `create page G52.Probe (Title: 'Probe', Layout: Atlas_Core.PopupLayout, Params: ( $Gate: G52.Gate )) { + dataview dvP (DataSource: $Gate) { + container cWrap { + actionbutton btnInContainer (Caption: 'c', Action: microflow G52.ACT_Open(Gate = $dvP)) + } + actionbutton btnOwnPath (Caption: 'p', Action: microflow G52.ACT_Str(S = $dvP/Name)) + actionbutton btnLiteral (Caption: 'l', Action: microflow G52.ACT_Str(S = '$dvP')) + datagrid dgIn (DataSource: database from G52.Gate) { + controlbar cb1 { + actionbutton btnCtlBar (Caption: 'cb', Action: microflow G52.ACT_Open(Gate = $dvP)) + } + column Name (Attribute: Name) { + actionbutton btnInColumn (Caption: 'col', Action: microflow G52.ACT_Open(Gate = $dvP)) + } + } + listview lvIn (DataSource: database from G52.Gate) { + actionbutton btnInList (Caption: 'li', Action: microflow G52.ACT_Open(Gate = $dvP)) + actionbutton btnLvOwn (Caption: 'lo', Action: microflow G52.ACT_Open(Gate = $lvIn)) + } + } + datagrid dgSelf (DataSource: database from G52.Gate, Selection: Single) { + controlbar cb2 { + actionbutton btnSelCtl (Caption: 'sel', Action: microflow G52.ACT_Open(Gate = $dgSelf)) + } + column Name (Attribute: Name) { + actionbutton btnDgOwnCol (Caption: 'dg', Action: microflow G52.ACT_Open(Gate = $dgSelf)) + } + } + gallery gaSelf (DataSource: database from G52.Gate) { + template t1 { + actionbutton btnGaOwn (Caption: 'ga', Action: microflow G52.ACT_Open(Gate = $gaSelf)) + } + } +};` + got := strings.Join(ownContainerFlagged(t, src), ",") + want := "btnCtlBar,btnDgOwnCol,btnGaOwn,btnInContainer,btnLvOwn,btnOwnPath" + if got != want { + t.Fatalf("flagged %q, want %q (mx check 11.14.0's CE0117 set)", got, want) + } +} + +// The suggestion is the spelling that works where the button is. +func TestValidatePageButtonContext_OwnContainerName_Suggestion(t *testing.T) { + prog, errs := visitor.Build(`create page P.X (Title: 'x', Layout: Atlas_Core.PopupLayout, Params: ( $O: P.O )) { + dataview dvO (DataSource: $O) { + actionbutton b1 (Caption: 'b', Action: microflow P.F(O = $dvO)) + } +};`) + if len(errs) > 0 { + t.Fatalf("parse errors: %v", errs) + } + vs := ValidatePageButtonContext(prog) + if len(vs) != 1 || vs[0].RuleID != "MDL-BUTTON02" { + t.Fatalf("want one MDL-BUTTON02, got %+v", vs) + } + if !strings.Contains(vs[0].Message, "CE0117") || !strings.Contains(vs[0].Suggestion, "$currentObject") { + t.Errorf("message should name CE0117 and suggest $currentObject: %+v", vs[0]) + } +}