MPS Actions Aspect
The actions aspect customizes how new nodes of a concept are constructed by the MPS editor — typically during substitution, side transformations, right-transform/<ctrl-space> replacement, or auto-replace. The framework produces an empty instance of the target concept; the setup function then populates it, often copying fields from the node it is replacing (sampleNode). Lives in <lang>/models/<lang>.actions.mps, language jetbrains.mps.lang.actions. The aspect also hosts copy/paste handlers and paste wrappers (see references/copy-paste-and-paste-wrappers.md).
Critical Directives
- One
NodeFactories root per actions model; add per-concept NodeFactory children to it. Do not create one root per concept.
- One
NodeFactory per applicableConcept. Multiple factories for the same concept are not merged.
- The setup function runs before
newNode is attached to the model. newNode.parent, ancestors, and model-wide queries return null/empty. Use the enclosingNode implicit parameter when you need the would-be parent.
sampleNode is typed as node<BaseConcept>. Always narrow with ifInstanceOf (sampleNode is <Concept> original) before accessing fields — direct dereference will NPE or fail to compile.
- Use
SPropertyAccess (ref property → PropertyDeclaration) for properties; use SLinkAccess (ref link → LinkDeclaration) for both containment and reference child roles.
- Factories do not fire for quotations
<C()>, model.add root(<C()>), model.add new root(C), or pure rendering. If you need factory-initialised nodes in generator code, use new node<C>() with explicit property assignments or route creation through an intention that calls add new initialized.
- Edit factories through MPS MCP tools (
mps_mcp_insert_root_node_from_json, mps_mcp_update_node, optionally mps_mcp_parse_java_and_insert with featureKind: "STATEMENTS" for the body). Do not hand-edit .mps files.
- After editing, validate with
mps_mcp_check_root_node_problems on the NodeFactories root and rebuild the language.
Common-Path Workflow
- Ensure an actions model exists (
<lang>/models/<lang>.actions.mps; create with mps_mcp_create_model and modelName: "<lang>.actions" — aspect ID actions, case-sensitive, no @ suffix; see aspect-model-stereotypes.md). Used languages: jetbrains.mps.lang.actions, jetbrains.mps.baseLanguage, jetbrains.mps.lang.smodel. Add jetbrains.mps.lang.core as the base. Import the structure model of your language.
- Create the
NodeFactories root via mps_mcp_insert_root_node_from_json (blueprint in references/json-blueprints.md). Set name.
- For each concept that needs custom initialization, add a
NodeFactory child via mps_mcp_update_node. Set applicableConcept; attach a NodeSetupFunction with a StatementList body.
- Fill the body. Typical pattern:
ifInstanceOf (sampleNode is <Concept> original) { newNode.<prop> = original.<prop>; ... }. Cross-type narrowing is allowed — see references/setup-function-bodies.md.
- Validate with
mps_mcp_check_root_node_problems, rebuild the language, exercise in a sandbox.
Implicit Parameters of NodeSetupFunction
| Alias |
FQN |
Type |
Nullable |
Notes |
newNode |
jetbrains.mps.lang.actions.structure.NodeSetupFunction_NewNode |
node<applicableConcept> |
no |
The freshly created empty node. Populate it. |
sampleNode |
jetbrains.mps.lang.actions.structure.NodeSetupFunction_SampleNode |
node<BaseConcept> |
yes |
Node being replaced, if any. Narrow with ifInstanceOf. |
enclosingNode |
jetbrains.mps.lang.actions.structure.NodeSetupFunction_EnclosingNode |
node<BaseConcept> |
yes |
Prospective parent container; null during root-node creation or programmatic creation outside the editor. |
index |
jetbrains.mps.lang.actions.structure.NodeSetupFunction_Index |
int |
— |
0-based position in the parent collection, or 0 if not in a collection. |
model |
(plain VariableReference in generated Java) |
SModel |
yes |
The SModel newNode will be inserted into. Most factories don't use this. |
Signature surfaced in the editor: (newNode, sampleNode, enclosingNode, index, model) -> void.
Related Skills
mps-aspect-behavior — concept constructors are the place to set defaults that don't depend on the node being replaced. Use a constructor for intrinsic defaults, a NodeFactory for context-sensitive carry-over.
mps-aspect-intentions — intentions that call add new initialized(...) route through factories. The NF_* family is documented in the intentions skill's references/factory-initialized.md.
mps-aspect-generator — generator output bypasses factories (quotations and add new root do not fire factories). Document where the generator must mimic the factory behavior.
mps-model-manipulation — full smodel reference: SLinkAccess vs SPropertyAccess, IfInstanceOfStatement, IfInstanceOfVariable, and the NF_* family.
mps-aspect-structure-concepts — when adding the concept that applicableConcept targets.
mps-aspect-constraints — for canBe* rules that gate insertion before a factory ever runs.
Reference Index
- Open
references/node-factories-and-triggers.md when you need the validated concept ref for NodeFactories / NodeFactory / NodeSetupFunction, the rule that factories are picked by exact applicableConcept (not by subtype inheritance through a more-general parent factory), and the precise list of triggers that fire / do not fire a factory.
- Open
references/setup-function-bodies.md when writing the function body — copying properties vs. child links (SPropertyAccess vs SLinkAccess), cross-type narrowing in ifInstanceOf, the verbatim ChemMastery (same-type property copy) and Kaja (cross-type child-link copy) examples, and the JSON shape for an AssignmentExpression over SLinkAccess.
- Open
references/json-blueprints.md when inserting the NodeFactories root or a NodeFactory child via MCP — minimal blueprints, the full IfInstanceOfStatement body, and the tip on using mps_mcp_parse_java_and_insert to skip the blueprint.
- Open
references/copy-paste-and-paste-wrappers.md when the language needs custom copy pre-processing, paste post-processing, or paste-time wrapping of nodes — these other roots also live in the actions aspect.
- Open
references/concept-reference.md for the validated FQN/ref table — the jetbrains.mps.lang.actions concepts plus the supporting BaseLanguage and smodel concepts used in setup bodies.
- Open
references/common-failures.md when a freshly created node always has defaults, sampleNode.<prop> does not compile, enclosingNode is unexpectedly null, the factory fires twice, or it does not fire from quotations / generator code.
1---2name: mps-aspect-actions3description: Use when defining or editing MPS node factories (the "actions" aspect) — `NodeFactories` roots, per-concept `NodeFactory` setup functions that initialize a freshly created node and optionally copy data from a replaced `sampleNode`, plus the actions aspect's `CopyPasteHandlers` and `PasteWrappers` roots. Reach for this skill when a substitution, side transform, completion replacement, or `add new initialized(...)` should preserve fields from the node it is replacing, or when defaults set in a constructor are not enough.4---56# MPS Actions Aspect78The **actions** aspect customizes how new nodes of a concept are constructed by the MPS editor — typically during substitution, side transformations, right-transform/`<ctrl-space>` replacement, or auto-replace. The framework produces an empty instance of the target concept; the setup function then populates it, often copying fields from the node it is replacing (`sampleNode`). Lives in `<lang>/models/<lang>.actions.mps`, language `jetbrains.mps.lang.actions`. The aspect also hosts copy/paste handlers and paste wrappers (see `references/copy-paste-and-paste-wrappers.md`).910## Critical Directives1112- One `NodeFactories` root per actions model; add per-concept `NodeFactory` children to it. Do not create one root per concept.13- One `NodeFactory` per `applicableConcept`. Multiple factories for the same concept are not merged.14- The setup function runs **before** `newNode` is attached to the model. `newNode.parent`, ancestors, and model-wide queries return null/empty. Use the `enclosingNode` implicit parameter when you need the would-be parent.15- `sampleNode` is typed as `node<BaseConcept>`. Always narrow with `ifInstanceOf (sampleNode is <Concept> original)` before accessing fields — direct dereference will NPE or fail to compile.16- Use `SPropertyAccess` (ref `property` → `PropertyDeclaration`) for properties; use `SLinkAccess` (ref `link` → `LinkDeclaration`) for **both** containment and reference child roles.17- Factories do **not** fire for quotations `<C()>`, `model.add root(<C()>)`, `model.add new root(C)`, or pure rendering. If you need factory-initialised nodes in generator code, use `new node<C>()` with explicit property assignments or route creation through an intention that calls `add new initialized`.18- Edit factories through MPS MCP tools (`mps_mcp_insert_root_node_from_json`, `mps_mcp_update_node`, optionally `mps_mcp_parse_java_and_insert` with `featureKind: "STATEMENTS"` for the body). Do not hand-edit `.mps` files.19- After editing, validate with `mps_mcp_check_root_node_problems` on the `NodeFactories` root and rebuild the language.2021## Common-Path Workflow22231. Ensure an actions model exists (`<lang>/models/<lang>.actions.mps`; create with `mps_mcp_create_model` and `modelName: "<lang>.actions"` — aspect ID `actions`, case-sensitive, no `@` suffix; see [aspect-model-stereotypes.md](../mps-mcp-workflow/references/aspect-model-stereotypes.md)). Used languages: `jetbrains.mps.lang.actions`, `jetbrains.mps.baseLanguage`, `jetbrains.mps.lang.smodel`. Add `jetbrains.mps.lang.core` as the base. Import the structure model of your language.242. Create the `NodeFactories` root via `mps_mcp_insert_root_node_from_json` (blueprint in `references/json-blueprints.md`). Set `name`.253. For each concept that needs custom initialization, add a `NodeFactory` child via `mps_mcp_update_node`. Set `applicableConcept`; attach a `NodeSetupFunction` with a `StatementList` body.264. Fill the body. Typical pattern: `ifInstanceOf (sampleNode is <Concept> original) { newNode.<prop> = original.<prop>; ... }`. Cross-type narrowing is allowed — see `references/setup-function-bodies.md`.275. Validate with `mps_mcp_check_root_node_problems`, rebuild the language, exercise in a sandbox.2829## Implicit Parameters of `NodeSetupFunction`3031| Alias | FQN | Type | Nullable | Notes |32|---|---|---|---|---|33| `newNode` | `jetbrains.mps.lang.actions.structure.NodeSetupFunction_NewNode` | `node<applicableConcept>` | no | The freshly created empty node. Populate it. |34| `sampleNode` | `jetbrains.mps.lang.actions.structure.NodeSetupFunction_SampleNode` | `node<BaseConcept>` | yes | Node being replaced, if any. Narrow with `ifInstanceOf`. |35| `enclosingNode` | `jetbrains.mps.lang.actions.structure.NodeSetupFunction_EnclosingNode` | `node<BaseConcept>` | yes | Prospective parent container; null during root-node creation or programmatic creation outside the editor. |36| `index` | `jetbrains.mps.lang.actions.structure.NodeSetupFunction_Index` | `int` | — | 0-based position in the parent collection, or 0 if not in a collection. |37| `model` | (plain VariableReference in generated Java) | `SModel` | yes | The `SModel` `newNode` will be inserted into. Most factories don't use this. |3839Signature surfaced in the editor: `(newNode, sampleNode, enclosingNode, index, model) -> void`.4041## Related Skills4243- `mps-aspect-behavior` — concept constructors are the place to set defaults that don't depend on the node being replaced. Use a constructor for *intrinsic* defaults, a `NodeFactory` for context-sensitive carry-over.44- `mps-aspect-intentions` — intentions that call `add new initialized(...)` route through factories. The `NF_*` family is documented in the intentions skill's `references/factory-initialized.md`.45- `mps-aspect-generator` — generator output bypasses factories (quotations and `add new root` do not fire factories). Document where the generator must mimic the factory behavior.46- `mps-model-manipulation` — full smodel reference: `SLinkAccess` vs `SPropertyAccess`, `IfInstanceOfStatement`, `IfInstanceOfVariable`, and the `NF_*` family.47- `mps-aspect-structure-concepts` — when adding the concept that `applicableConcept` targets.48- `mps-aspect-constraints` — for `canBe*` rules that gate insertion before a factory ever runs.4950## Reference Index5152- Open `references/node-factories-and-triggers.md` when you need the validated concept ref for `NodeFactories` / `NodeFactory` / `NodeSetupFunction`, the rule that factories are picked by exact `applicableConcept` (not by subtype inheritance through a more-general parent factory), and the precise list of triggers that fire / do not fire a factory.53- Open `references/setup-function-bodies.md` when writing the function body — copying properties vs. child links (`SPropertyAccess` vs `SLinkAccess`), cross-type narrowing in `ifInstanceOf`, the verbatim ChemMastery (same-type property copy) and Kaja (cross-type child-link copy) examples, and the JSON shape for an `AssignmentExpression` over `SLinkAccess`.54- Open `references/json-blueprints.md` when inserting the `NodeFactories` root or a `NodeFactory` child via MCP — minimal blueprints, the full `IfInstanceOfStatement` body, and the tip on using `mps_mcp_parse_java_and_insert` to skip the blueprint.55- Open `references/copy-paste-and-paste-wrappers.md` when the language needs custom copy pre-processing, paste post-processing, or paste-time wrapping of nodes — these other roots also live in the actions aspect.56- Open `references/concept-reference.md` for the validated FQN/ref table — the `jetbrains.mps.lang.actions` concepts plus the supporting BaseLanguage and smodel concepts used in setup bodies.57- Open `references/common-failures.md` when a freshly created node always has defaults, `sampleNode.<prop>` does not compile, `enclosingNode` is unexpectedly null, the factory fires twice, or it does not fire from quotations / generator code.