From ca96901a194d6d2be13da2b7f80ffba20e134b29 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 18:04:09 +0000 Subject: [PATCH] fix(check): MDL-BUTTON02 covers data sources and widget expressions A data container's widget-name variable is in scope only one data container below it, and that holds for every slot evaluated in a widget's enclosing context, not only action arguments. Measured on mxbuild 11.14.0, each against a control one data view deeper that builds clean: a nested widget's microflow data-source argument, Visible, Editable and DynamicClasses reading the container they sit in directly are CE0117, and a nested list's XPath `where` is CE0161. All five passed check and exec. checkOwnContainerName now takes the widget and walks its action arguments, data-source arguments and XPath, and those expression properties, naming the CE code per slot. Text-template parameters are left alone: MDL-WIDGET24 already refuses a variable path there. Refs 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 | 2 +- .../mendix/create-page/reference/widgets.md | 5 +- CHANGELOG.md | 2 +- ...ta-container-name-outside-actions.fail.mdl | 30 +++++++ mdl/executor/validate_page_button_context.go | 84 ++++++++++++------- .../validate_page_own_container_name_test.go | 45 ++++++++++ 7 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index 935d6b987..9cc1fe60c 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -875,3 +875,4 @@ {"area": "mdl-executor", "date": "2026-10-06", "symptom": "`create constant M.Flag (Type: Boolean, DefaultValue: true)` (or `True`) stores DefaultValue \"true\". Studio Pro stores \"True\"/\"False\"; its constant dialog shows a stored \"true\" as False while the runtime reads it as true, so the developer sees one value and the app runs with another. `mx check` is silent and `describe constant` prints `true` for both, so only the stored unit shows it. Quoted `'True'` was the workaround. `alter settings constant @M.Flag value true` (or 'true') wrote the configuration override the same way", "cause": "createConstant rendered the AST literal with fmt.Sprintf(\"%v\"), so a Go bool became Go's lowercase \"true\"; the quoted string passed through verbatim, which is why only 'True' was right. The settings path stored the visitor's token text verbatim and never looked at the constant's type", "file": "`mdl/executor/cmd_constants.go` (storedConstantDefault, used by the create and create-or-modify branches, and by `alterSettingsConstant` in `mdl/executor/cmd_settings.go` via settingsConstantType); test `mdl/executor/cmd_constant_boolean_default_test.go`; bug-test `mdl-examples/bug-tests/1321-boolean-constant-default-case.mdl`", "insight": "**`%v` on an AST literal is Go's spelling, not Mendix's**: any place that stringifies a parsed value into a stored property must normalise to the platform's form. The case-insensitive reader (formatDefaultValue's EqualFold) hid the writer's defect from describe, so a round-trip test could not catch it; assert on the value handed to the backend. Neither `mx check` nor the runtime distinguishes the two spellings — only Studio Pro's dialog does — so a build cannot verify this; the evidence is the reporter's Studio Pro 11.12.4 measurement plus the stored unit. Control: test written before the fix failed 5 of 6 spellings with `stored as \"true\"`/`\"false\"`, the quoted 'True' control passing; exec on a copy of testdata/pedapp then stores True/True/True/False. **Enumerate every write path for the value, not just the reported one**: the configuration override holds the same typed value and had the same defect; it needs the constant's type looked up (a String constant holding 'true' must stay lowercase). Control: the settings test failed 3 of 5 cases with `stored as \"true\"`/`\"FALSE\"` before the fix, the 'True' and String-constant controls passing; the stored Settings unit on a pedapp copy read 'true' for `value true` and `value 'true'` before, 'True' after, the String override 'true' both times", "refs": ["mendixlabs/mxcli#1321"]} {"area": "mdl/executor", "date": "2026-10-07", "symptom": "A button added to a snippet with `alter snippet … insert` (or replace) calling `call microflow M.F(P = $P)` with a snippet parameter fails mxbuild with CE0115 \"The arguments that are passed to microflow 'M.F' do not match the expected parameters and need to be refreshed\" on that button only; the same button in CREATE SNIPPET or via ALTER SNIPPET SET is clean", "cause": "`buildWidgetsFromAST` and `buildColumnSpecsFromAST` (mdl/executor/cmd_alter_page.go) seeded paramScope and localVariables from the mutator but not `isSnippet`, so `classifyFlowArgValue` returned kind \"parameter\" and the Forms$PageVariable named PageParameter instead of SnippetParameter", "file": "`mdl/executor/cmd_alter_page.go` (isSnippet from mutator.ContainerType() in both builders)", "insight": "**A pageBuilder built outside CREATE needs THREE scope fields, not two** — paramScope, localVariables and isSnippet; #1317 found the SET builder missing all three and this one missing the last. When adding a builder, copy the seeding from convertASTAction rather than from memory. **Unlike the Expression-form defect (#1140/#1317), mxbuild DOES catch the wrong slot** — CE0115 rather than CE1571 — so one script exercising CREATE, INSERT and SET on the same snippet with `mx check` is a complete A/B: only the broken path's button is named. Verified on 11.12.2: 1 error before, 0 after", "refs": ["mendixlabs/mxcli#1317", "mendixlabs/mxcli#1140"], "ce": ["CE0115"]} {"area": "mdl/executor", "date": "2026-10-07", "symptom": "`set $N = 1;` on a microflow/nanoflow/rule PARAMETER ($N: Integer or String) passes `mxcli check` and `exec`; mx check reports [CE7247] \"Parameter 'N' cannot be changed.\" at the Change variable activity", "cause": "Nothing modelled that a Change variable activity cannot target a parameter; the variable-kind tracking treated a primitive parameter like any declared primitive. Found while measuring MDL-SET01's object producers (#1323): the object parameter's CE7247 came back with a different wording, which was the tell that the restriction is on parameters, not on types", "file": "`mdl/executor/validate_microflow_set_object.go` (`checkSetOnObjectVariable` primitive-parameter branch, `setTargetViolations`); rules via `validate_program.go` (check) and `rule_validation.go` `validateRuleSetTargets` (exec, `cmd_rules_create.go`); MDL-SET01 added to `execEnforcedMicroflowRules` in `validate.go`", "insight": "When one measurement returns a different MESSAGE under the same CE code, test the cause the message names — here 'Parameter … cannot be changed' fired for an Integer too, a whole adjacent class. Measure the neighbours before writing the rule: list parameters (`set` = Change list Replace, `add`) and member changes on parameters BUILD, so the rule is 'any non-list parameter', not 'any parameter'. Rules never run ValidateMicroflow: plain check needs a ValidateProgram hook and exec a gate in cmd_rules_create; putting it in validateRule instead would double-print under --references, which runs both", "refs": ["mendixlabs/mxcli#1323"], "ce": ["CE7247"], "rules": ["MDL-SET01"]} +{"area": "mdl-executor", "date": "2026-10-07", "refs": ["mendixlabs/mxcli#1324"], "symptom": "A widget directly inside data view `dvP` that reads `$dvP` OUTSIDE an action — a nested data view's `DataSource: microflow M.F(Gate = $dvP)`, `Visible: $dvP/Name != ''`, `Editable: …`, `DynamicClasses: …`, or a nested list's `database from M.E where [Name = $dvP/Name]` — passes `check` and `exec`, then `mx check` reports `[CE0117] \"Error(s) in expression.\"` at the widget (CE0161 \"Error(s) in XPath constraint.\" for the `where`)", "cause": "MDL-BUTTON02 (the #1324 fix) only looked at action arguments, though every slot evaluated in the widget's enclosing context has the same scope: a container's widget-name variable exists only one data container below it", "file": "`mdl/executor/validate_page_button_context.go` (`checkOwnContainerName` now takes the widget and walks action args, `GetDataSource().Args` / `.Where`, and `ownNameExprProps`)", "insight": "**A scope rule belongs to the context, not to the slot the report happened to use.** The follow-up question that settled it was one probe per slot with a control one data view deeper: five of five slots failed in the own context and all five controls built clean, so the rule is 'anything evaluated in this widget's enclosing context' — enumerate those slots rather than wait for one report each. Two things that would have wasted a build: (1) `Visible:`/`Editable:` arrive in the AST as `VisibleIf`/`EditableIf` strings (dump `w.Properties` before keying on what the author wrote), and (2) text-template parameters (`ContentParams ({1} = $dvP/Name)`) are already refused by MDL-WIDGET24 for an unrelated reason (a template parameter is an attribute name, not a variable path), so they are not a scope case at all. The XPath slot reports a different code (CE0161), which is why the message carries the code per slot. Control: stubbing the data-source and property slots fails the new test with `flagged \"\"`; the `$currentObject` rewrite of all five builds at 0 errors. Repro `mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl`"} diff --git a/.claude/skills/mendix/cheatsheet-errors/SKILL.md b/.claude/skills/mendix/cheatsheet-errors/SKILL.md index 002af66d6..e7c8e4b25 100644 --- a/.claude/skills/mendix/cheatsheet-errors/SKILL.md +++ b/.claude/skills/mendix/cheatsheet-errors/SKILL.md @@ -265,7 +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 | +| 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. Same for data-source arguments, `Visible:`, `Editable:`, `DynamicClasses:`, and (as CE0161) a nested list's XPath `where`. 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 24d650928..123aa12e4 100644 --- a/.claude/skills/mendix/create-page/reference/widgets.md +++ b/.claude/skills/mendix/create-page/reference/widgets.md @@ -1073,7 +1073,10 @@ 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 +MDL-BUTTON02). That holds for every slot evaluated there, not only action +arguments: a nested widget's microflow data-source arguments, `Visible:`, +`Editable:` and `DynamicClasses:` (CE0117), and a nested list's XPath `where` +(**CE0161**). 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). diff --git a/CHANGELOG.md b/CHANGELOG.md index fd508e07e..a976c9ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed - **`set $Param = …` on a parameter is refused** — a Change variable cannot target a parameter, and mxbuild rejects it with CE7247 "Parameter 'N' cannot be changed." (measured on 11.14.0 for Integer and String parameters in a microflow, a nanoflow and a rule). `check` and `exec` passed it. MDL-SET01 now refuses it for every parameter but a list (`set` on a list parameter is a Change list Replace, which builds), and `exec` enforces the rule. Copy the parameter into a variable first: `declare $Value Integer = $N;`. -- **`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. +- **`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. The rule covers every slot evaluated in that context, not only action arguments: a nested widget's microflow data-source arguments, `Visible:`, `Editable:` and `DynamicClasses:` (CE0117), and a nested list's XPath `where` (CE0161). 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. - **`set $Obj = …` on an object variable is refused** (mendixlabs/mxcli#1323) — with both variables single objects (e.g. a Reference retrieved from its FROM entity), `set $Cursor = $Next;` passed `check --references` and `exec` and was written as a Change variable action, which mxbuild refuses with CE7247 "Variable 'Cursor' does not have a primitive type". Mendix has no action that reassigns an object variable, so `check` (MDL-SET01, for the objects it can see without a project), `check --references` and `exec` now refuse it and name the alternatives (a sub-microflow that returns the next object, `change $Obj (…)`). `check --references` now types a Reference retrieve from its FROM entity as the object it is. `set` on a list variable stays a Change list Replace (ako/mxcli#949). - **`RENAME JAVA ACTION` renames the Java class, not just the file** — renaming `M.JA_Old` to `JA_New` moved the source to `JA_New.java` but left `public class JA_Old`, its constructor and its `toString` inside it, which javac rejects (`class JA_Old is public, should be declared in a file named JA_Old.java`). A full build hid it, because mxbuild regenerates the stub in its own copy; `run --local --watch` hot reload and IDEs compile the file on disk. Those three places now follow the rename exactly as mxbuild rewrites them; user code and extra code are left as written. Applies to `mxcli rename java-action` too. - **`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. diff --git a/mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl b/mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl new file mode 100644 index 000000000..f09cead85 --- /dev/null +++ b/mdl-examples/bug-tests/1324-own-data-container-name-outside-actions.fail.mdl @@ -0,0 +1,30 @@ +-- mendixlabs/mxcli#1324 follow-up: a data container's own name outside actions +-- +-- NEGATIVE TEST (.fail.mdl) — EXPECTED to fail `mxcli check`. +-- `make check-mdl` inverts the exit code: an unexpected pass is a regression of +-- MDL-BUTTON02's non-action slots. +-- +-- Symptom: `check` and `exec` were clean, then `mx check` (Mendix 11.14.0) +-- reported, for widgets placed directly inside data view dvP: +-- [CE0117] "Error(s) in expression." at Container 'cVisOwn' +-- and the same for a nested data view's microflow data-source argument, +-- `Editable:` and `DynamicClasses:`, plus CE0161 "Error(s) in XPath +-- constraint." for a nested list's `where`. These slots are evaluated in the +-- enclosing context, where dvP's object is $currentObject; `$dvP` is a +-- variable only one data container deeper (cVisOuter builds clean). + +create module G52; +create persistent entity G52.Gate (Name: String(50)); + +create page G52.VisibilityPage (Title: 'Gate', Layout: Atlas_Core.PopupLayout, Params: ( $Gate: G52.Gate )) { + dataview dvP (DataSource: $Gate) { + container cVisOwn (Visible: $dvP/Name != '') { + dynamictext dtA (Content: 'a') + } + dataview dvMid (DataSource: $Gate) { + container cVisOuter (Visible: $dvP/Name != '') { + dynamictext dtB (Content: 'b') + } + } + } +}; diff --git a/mdl/executor/validate_page_button_context.go b/mdl/executor/validate_page_button_context.go index d8ea51bc6..63482465d 100644 --- a/mdl/executor/validate_page_button_context.go +++ b/mdl/executor/validate_page_button_context.go @@ -56,13 +56,11 @@ func checkButtonContextTree(widgets []*ast.WidgetV3, controlBarOf string, inCont if w == nil { continue } - 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)...) - } + if a := w.GetAction(); a != nil && controlBarOf != "" && !inContext { + out = append(out, checkControlBarAction(a, w.Name, controlBarOf, locationPrefix)...) + } + if nearest != "" { + out = append(out, checkOwnContainerName(w, nearest, locationPrefix)...) } childContext := inContext || isObjectContextContainer(w) childNearest := nearest @@ -80,33 +78,59 @@ func checkButtonContextTree(widgets []*ast.WidgetV3, controlBarOf string, inCont 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 { +// ownNameExprProps are the expression-valued widget properties evaluated in +// the widget's enclosing context, keyed as the visitor stores them (`Visible:` +// and `Editable:` expressions land under the *If keys) and named as written. +var ownNameExprProps = []struct{ key, written string }{ + {"VisibleIf", "Visible"}, {"EditableIf", "Editable"}, {"DynamicClasses", "DynamicClasses"}, +} + +// checkOwnContainerName flags a widget that reads the nearest data container +// by its widget name — `$dvGate` from a widget 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 (mendixlabs/mxcli#1324). +// +// Every slot here is evaluated in the widget's enclosing context, and each was +// measured on mxbuild 11.14.0 against a control one data view deeper: action +// arguments (and a chained THEN action's), a data source's flow arguments, +// Visible, Editable and DynamicClasses are CE0117 "Error(s) in expression.", +// and a data source's XPath `where` is CE0161 "Error(s) in XPath constraint.". +func checkOwnContainerName(w *ast.WidgetV3, nearest, locationPrefix string) []linter.Violation { var out []linter.Violation - for a != nil { + flag := func(slot, expr, code string) { + if !exprReadsVariable(expr, nearest) { + return + } + out = append(out, linter.Violation{ + RuleID: "MDL-BUTTON02", + Severity: linter.SeverityError, + Message: fmt.Sprintf( + "%s: `%s` reads `%s` in its %s, but `%s` is the data container the widget sits in directly — its name is a variable only inside a data container nested below it (%s)", + locationPrefix, w.Name, expr, slot, nearest, code), + 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), + }) + } + for a := w.GetAction(); a != nil; a = a.ThenAction { for _, arg := range a.Args { - s, ok := arg.Value.(string) - if !ok || !exprReadsVariable(s, nearest) { - continue + if v, ok := arg.Value.(string); ok { + flag(a.Type+" action argument", v, "CE0117") } - 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 + } + if ds := w.GetDataSource(); ds != nil { + for _, arg := range ds.Args { + if v, ok := arg.Value.(string); ok { + flag("data source argument", v, "CE0117") + } + } + flag("data source XPath", ds.Where, "CE0161") + } + for _, p := range ownNameExprProps { + if v, ok := w.Properties[p.key].(string); ok { + flag(p.written+" expression", v, "CE0117") + } } return out } diff --git a/mdl/executor/validate_page_own_container_name_test.go b/mdl/executor/validate_page_own_container_name_test.go index d2f062957..b92225201 100644 --- a/mdl/executor/validate_page_own_container_name_test.go +++ b/mdl/executor/validate_page_own_container_name_test.go @@ -116,3 +116,48 @@ func TestValidatePageButtonContext_OwnContainerName_Suggestion(t *testing.T) { t.Errorf("message should name CE0117 and suggest $currentObject: %+v", vs[0]) } } + +// The same scope rule holds outside actions. Measured with `mx check` 11.14.0 +// (each slot once directly in dvP, once one data view deeper as the control): +// a nested data view's microflow data-source argument, Visible, Editable and +// DynamicClasses are CE0117 and a nested list's XPath `where` is CE0161 when +// they read the container they sit in directly; every control builds clean. +func TestValidatePageButtonContext_OwnContainerName_OutsideActions(t *testing.T) { + src := `create page G52.Probe3 (Title: 'Probe3', Layout: Atlas_Core.PopupLayout, Params: ( $Gate: G52.Gate )) { + dataview dvP (DataSource: $Gate) { + dataview dvDsOwn (DataSource: microflow G52.DS_Gate(Gate = $dvP)) { + dynamictext dtA (Content: 'a') + } + container cVisOwn (Visible: $dvP/Name != '') { + dynamictext dtB (Content: 'b') + } + textbox tbEdOwn (Attribute: Name, Editable: $dvP/Name != '') + container cClsOwn (DynamicClasses: if $dvP/Name = '' then 'a' else 'b') { + dynamictext dtE (Content: 'e') + } + listview lvXpOwn (DataSource: database from G52.Gate where [Name = $dvP/Name]) { + dynamictext dtF (Content: 'f') + } + dataview dvMid (DataSource: $Gate) { + dataview dvDsOuter (DataSource: microflow G52.DS_Gate(Gate = $dvP)) { + dynamictext dtC (Content: 'c') + } + container cVisOuter (Visible: $dvP/Name != '') { + dynamictext dtD (Content: 'd') + } + textbox tbEdOuter (Attribute: Name, Editable: $dvP/Name != '') + container cClsOuter (DynamicClasses: if $dvP/Name = '' then 'a' else 'b') { + dynamictext dtG (Content: 'g') + } + listview lvXpOuter (DataSource: database from G52.Gate where [Name = $dvP/Name]) { + dynamictext dtH (Content: 'h') + } + } + } +};` + got := strings.Join(ownContainerFlagged(t, src), ",") + want := "cClsOwn,cVisOwn,dvDsOwn,lvXpOwn,tbEdOwn" + if got != want { + t.Fatalf("flagged %q, want %q (mx check 11.14.0's CE0117/CE0161 set)", got, want) + } +}