Skip to content

feat(lint): CONV020 — object created in a microflow and never committed, returned or passed on (mendixlabs/mxcli#1518) - #1532

Merged
ako merged 1 commit into
mainfrom
feature/1518-lint-created-object-never-persisted
Oct 21, 2026
Merged

ako merged 1 commit into
mainfrom
feature/1518-lint-created-object-never-persisted

Conversation

@ako

@ako ako commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Implements mendixlabs#1518 (#1400), approved by the maintainer. This is a conservative rule at warning severity.

Rule: CONV020 (quality, warning)

CONV020 sits in mdl/linter/rules/conv_created_never_persisted.go. It flags a create when all of these hold:

  • it is in a microflow (not a nanoflow or a rule);
  • it is a CreateObjectAction with Commit stored explicitly as No;
  • its entity is persistent, per the catalog;
  • its output variable is never used in a way that keeps the object.

It walks the stored object graph (FullMicroflow, like CONV011).

Escapes. These are flow-insensitive, include loops and error handlers, and match whole words regardless of case. Any of the following keeps the rule quiet:

  • the variable as a commit, delete or rollback target;
  • a change with commit;
  • the return value;
  • any call argument (microflow, Java action, JavaScript action, workflow, export mapping);
  • a show-page argument or passed object;
  • added to a list;
  • set as an association value on another object or on a new object;
  • any bare $o in an expression.

Only reading a member ($o/Attr) or changing the object without commit does not count as a use, so an unknown action type also keeps it quiet. A commit on only one branch is an accepted false negative.

Tests

conv_created_never_persisted_test.go, 34 subtests:

  • Positives: the issue's repro, a member read in a log message, a change without commit, a member read in a split or the return value, and a create inside a loop.
  • Negatives: one per escape, plus a commit only in the error handler, and the out-of-scope cases (non-persistent entity, unstored commit value, created with commit, no output variable).

Revert check:

  • With escape detection stubbed out, 19 negatives fail.
  • If member reads count as uses, the 4 non-escape positives fail.

End to end: on an 11.15 fixture copy, mdl-examples/bug-tests/1518-created-object-never-persisted.mdl gives exactly one CONV020. The controls stay silent, and mx check reports 0 errors, so nothing else catches this.

False-positive survey

  • Example scripts: 57 mdl-examples scripts containing create were executed into fresh fixture copies, then linted. That gave 70 creates with commit No and 7 hits, all read and all true positives: test fixtures whose object really is dropped.
  • Studio Pro-authored flows: the Administration module in the blank 11.15 project and in a 9.x app had 6 creates each and 0 hits. That is correct: Account is passed on as an association value.

Docs: the docs-site/src/tools/builtin-rules.md rule row and a details section, linting.md, the mendix/lint command table, the assess-quality skill, and a CHANGELOG entry. make build, make test and make lint pass.

For review

  • No check-time version. There is no mxcli check version of the rule, the way validate_commit_in_loop.go mirrors CONV011. That could be a follow-up.
  • Not in the REPL: like CONV011, the rule isn't in the REPL lint list.

Generated by Claude Code

…d on (mendixlabs#1518)

An object created with commit = No exists only in memory. If the
microflow never commits it, returns it or hands it to anything that
could, it is discarded when the microflow ends - and check, mx check and
the build are all silent. In the field an after-startup seed microflow
logged "Seeded 20" and the table had 0 rows.

CONV020 (warning, category quality) walks each microflow's stored object
graph via FullMicroflow, like CONV011. It reports a CreateObjectAction
with an explicit Commit = No whose entity the catalog lists as
persistent, when nothing else in the flow uses the output variable except
to read a member ($o/Attr) or change it without commit.

Every other mention counts as passing the object on: the walker scans
every string of every object in the flow (loops and error-handler paths
included) by reflection, so a field holding the variable name (commit,
delete, rollback, page passed object, workflow context, export mapping
source, ...) or a bare $o in any expression (return value, call
arguments, list change, association value) keeps the rule quiet,
including action types the rule does not know. It is flow-insensitive on
purpose: a commit on one branch only is an accepted false negative.
Nanoflows, rules, non-persistent entities, an unstored commit value and
System/Marketplace modules are out of scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
@ako
ako merged commit 6a59151 into main Oct 21, 2026
31 checks passed
ako pushed a commit that referenced this pull request Oct 21, 2026
Resolve the CHANGELOG conflict with #1532 (CONV020) by keeping both
"Added" entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3x8Fyvir8kLyPgJs1BDmF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants