@jinntec/fore 2.9.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +0 -2
  2. package/dist/fore-dev.js +9505 -6057
  3. package/dist/fore.js +9218 -5996
  4. package/index.js +1 -0
  5. package/package.json +4 -2
  6. package/src/DependentXPathQueries.js +37 -2
  7. package/src/ForeElementMixin.js +64 -38
  8. package/src/actions/fx-delete.js +424 -73
  9. package/src/actions/fx-insert.js +472 -73
  10. package/src/actions/fx-setattribute.js +5 -5
  11. package/src/actions/fx-setvalue.js +11 -9
  12. package/src/authoring-check.js +231 -0
  13. package/src/fore.js +32 -77
  14. package/src/functions/registerFunction.js +65 -20
  15. package/src/fx-bind.js +128 -73
  16. package/src/fx-fore.js +653 -219
  17. package/src/fx-instance.js +145 -143
  18. package/src/fx-model.js +292 -102
  19. package/src/fx-speech.js +268 -0
  20. package/src/fx-submission.js +246 -135
  21. package/src/fx-var.js +28 -4
  22. package/src/json/JSONDomFacade.js +84 -0
  23. package/src/json/JSONLens.js +67 -0
  24. package/src/json/JSONNode.js +248 -0
  25. package/src/json/lensFromNode.js +5 -0
  26. package/src/modelitem.js +16 -2
  27. package/src/tools/fx-action-log.js +1 -1
  28. package/src/ui/UIElement.js +16 -2
  29. package/src/ui/abstract-control.js +14 -7
  30. package/src/ui/fx-case.js +3 -1
  31. package/src/ui/fx-control.js +4 -2
  32. package/src/ui/fx-group.js +1 -1
  33. package/src/ui/fx-items.js +26 -32
  34. package/src/ui/fx-output.js +8 -38
  35. package/src/ui/fx-repeat-attributes.js +1 -1
  36. package/src/ui/fx-repeat.js +683 -247
  37. package/src/ui/fx-repeatitem.js +16 -1
  38. package/src/ui/repeat-base.js +9 -5
  39. package/src/withDraggability.js +0 -1
  40. package/src/xpath-evaluation.js +1763 -740
  41. package/src/xpath-path.js +274 -24
  42. package/src/xpath-util.js +92 -46
@@ -82,17 +82,20 @@ export default class FxSetvalue extends AbstractAction {
82
82
  */
83
83
  dispatchExecute() {}
84
84
 
85
+ // Adjustment in setValue logic to ensure we work with JSONNode, not just raw values
85
86
  setValue(modelItem, newVal) {
86
87
  console.log('setValue', modelItem, newVal);
87
88
  const item = modelItem;
88
89
  if (!item) return;
89
90
 
90
- if (item.value !== newVal) {
91
- // const path = XPathUtil.getPath(modelItem.node);
92
- const path = Fore.getDomNodeIndexString(modelItem.node);
91
+ // Check if current node is a JSONNode
92
+ const node = Array.isArray(item.node) ? item.node[0] : item.node;
93
93
 
94
+ if (item.value !== newVal) {
95
+ const path = Fore.getDomNodeIndexString(node);
94
96
  const ev = this.event;
95
97
  const targetElem = this;
98
+
96
99
  this.dispatchEvent(
97
100
  new CustomEvent('execute-action', {
98
101
  composed: true,
@@ -107,20 +110,19 @@ export default class FxSetvalue extends AbstractAction {
107
110
  }),
108
111
  );
109
112
 
113
+ // Use ModelItem's value setter which handles both DOM nodes and JSON lenses
110
114
  if (newVal?.nodeType) {
111
115
  if (newVal.nodeType === Node.ELEMENT_NODE) {
112
116
  item.value = newVal;
113
- }
114
- if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
115
- item.value = newVal.getValue();
116
- }
117
- if (newVal.nodeType === Node.TEXT_NODE) {
117
+ } else if (newVal.nodeType === Node.ATTRIBUTE_NODE) {
118
+ item.value = newVal.nodeValue;
119
+ } else if (newVal.nodeType === Node.TEXT_NODE) {
118
120
  item.value = newVal.textContent;
119
121
  }
120
122
  } else {
121
123
  item.value = newVal;
122
- item.node.textContent = newVal;
123
124
  }
125
+
124
126
  this.getModel().changed.push(modelItem);
125
127
  this.needsUpdate = true;
126
128
  modelItem.notify();
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Authoring integrity checks for Fore forms.
3
+ *
4
+ * Runs by default at startup. Add the `no-check` attribute to `<fx-fore>` to disable
5
+ * (e.g. in production). The module is dynamically imported, so it is never loaded
6
+ * when checks are disabled.
7
+ *
8
+ * Adding a new check: add a function `_check<Name>(fore, errors)` and call it in
9
+ * `checkAuthoring()` below.
10
+ */
11
+
12
+ const INSTANCE_RE = /instance\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
13
+ const INDEX_RE = /index\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
14
+
15
+ // Attributes that may carry XPath expressions
16
+ const XPATH_ATTRS = [
17
+ 'ref',
18
+ 'value',
19
+ 'calculate',
20
+ 'constraint',
21
+ 'required',
22
+ 'readonly',
23
+ 'relevant',
24
+ 'bind',
25
+ 'context',
26
+ 'if',
27
+ 'while',
28
+ 'origin',
29
+ 'iterate',
30
+ 'at',
31
+ ];
32
+
33
+ function _isDynamic(val) {
34
+ return !val || val.includes('{');
35
+ }
36
+
37
+ function _byId(fore, id) {
38
+ return (
39
+ fore.ownerDocument.getElementById(id) ||
40
+ fore.getRootNode().getElementById?.(id) ||
41
+ fore.querySelector(`#${id}`)
42
+ );
43
+ }
44
+
45
+ function _checkSendSubmissions(fore, errors) {
46
+ fore.querySelectorAll('fx-send[submission]').forEach(el => {
47
+ const id = el.getAttribute('submission');
48
+ if (_isDynamic(id)) return;
49
+ const localFore = el.closest('fx-fore');
50
+ const { model } = localFore;
51
+ const target = model
52
+ ? model.querySelector(`fx-submission#${id}`)
53
+ : fore.querySelector(`fx-submission#${id}`);
54
+ if (!target) {
55
+ errors.push({
56
+ element: el,
57
+ message: `<fx-send submission="${id}">: no <fx-submission id="${id}"> found`,
58
+ });
59
+ }
60
+ });
61
+ }
62
+
63
+ function _checkDispatchTargets(fore, errors) {
64
+ fore.querySelectorAll('fx-dispatch[targetid]').forEach(el => {
65
+ const id = el.getAttribute('targetid');
66
+ if (_isDynamic(id)) return;
67
+ if (!_byId(fore, id)) {
68
+ errors.push({
69
+ element: el,
70
+ message: `<fx-dispatch targetid="${id}">: no element with id="${id}" found`,
71
+ });
72
+ }
73
+ });
74
+ }
75
+
76
+ function _checkXPathInstanceRefs(fore, errors) {
77
+ const allEls = Array.from(fore.querySelectorAll('*'));
78
+ for (const el of allEls) {
79
+ const localFore = el.closest('fx-fore');
80
+ for (const attr of XPATH_ATTRS) {
81
+ const val = el.getAttribute(attr);
82
+ if (!val) continue;
83
+
84
+ INSTANCE_RE.lastIndex = 0;
85
+ let m;
86
+ while ((m = INSTANCE_RE.exec(val)) !== null) {
87
+ const id = m[1];
88
+ const localInstance = localFore.querySelector(`fx-instance#${id}`);
89
+ const sharedInstance =
90
+ !localInstance && localFore.ownerDocument.querySelector(`fx-instance[shared]#${id}`);
91
+ if (!localInstance && !sharedInstance) {
92
+ errors.push({
93
+ element: el,
94
+ message: `[${attr}="${val}"]: instance('${id}') — no <fx-instance id="${id}"> found`,
95
+ });
96
+ }
97
+ }
98
+
99
+ INDEX_RE.lastIndex = 0;
100
+ while ((m = INDEX_RE.exec(val)) !== null) {
101
+ const id = m[1];
102
+ if (!localFore.querySelector(`fx-repeat#${id}`)) {
103
+ errors.push({
104
+ element: el,
105
+ message: `[${attr}="${val}"]: index('${id}') — no <fx-repeat id="${id}"> found`,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ function _checkCallActions(fore, errors) {
114
+ fore.querySelectorAll('fx-call[action]').forEach(el => {
115
+ const id = el.getAttribute('action');
116
+ if (_isDynamic(id)) return;
117
+ if (!_byId(fore, id)) {
118
+ errors.push({
119
+ element: el,
120
+ message: `<fx-call action="${id}">: no element with id="${id}" found`,
121
+ });
122
+ }
123
+ });
124
+ }
125
+
126
+ function _checkShowHideDialogs(fore, errors) {
127
+ fore.querySelectorAll('fx-show[dialog], fx-hide[dialog]').forEach(el => {
128
+ const id = el.getAttribute('dialog');
129
+ if (_isDynamic(id)) return;
130
+ if (!_byId(fore, id)) {
131
+ errors.push({
132
+ element: el,
133
+ message: `<${el.localName} dialog="${id}">: no element with id="${id}" found`,
134
+ });
135
+ }
136
+ });
137
+ }
138
+
139
+ function _checkLoadAttachTo(fore, errors) {
140
+ fore.querySelectorAll('fx-load[attach-to]').forEach(el => {
141
+ const val = el.getAttribute('attach-to');
142
+ if (_isDynamic(val)) return;
143
+ if (!val.startsWith('#')) return; // _blank, _self etc. are valid non-id targets
144
+ const id = val.substring(1);
145
+ if (!_byId(fore, id)) {
146
+ errors.push({
147
+ element: el,
148
+ message: `<fx-load attach-to="${val}">: no element with id="${id}" found`,
149
+ });
150
+ }
151
+ });
152
+ }
153
+
154
+ function _checkRefreshControl(fore, errors) {
155
+ fore.querySelectorAll('fx-refresh[control]').forEach(el => {
156
+ const id = el.getAttribute('control');
157
+ if (_isDynamic(id)) return;
158
+ if (!_byId(fore, id)) {
159
+ errors.push({
160
+ element: el,
161
+ message: `<fx-refresh control="${id}">: no element with id="${id}" found`,
162
+ });
163
+ }
164
+ });
165
+ }
166
+
167
+ function _checkResetInstance(fore, errors) {
168
+ const model = fore.querySelector(':scope > fx-model');
169
+ fore.querySelectorAll('fx-reset[instance]').forEach(el => {
170
+ const id = el.getAttribute('instance');
171
+ if (_isDynamic(id)) return;
172
+ const target = model
173
+ ? model.querySelector(`fx-instance#${id}`)
174
+ : fore.querySelector(`fx-instance#${id}`);
175
+ const sharedTarget = !target && fore.ownerDocument.querySelector(`fx-instance[shared]#${id}`);
176
+ if (!target && !sharedTarget) {
177
+ errors.push({
178
+ element: el,
179
+ message: `<fx-reset instance="${id}">: no <fx-instance id="${id}"> found`,
180
+ });
181
+ }
182
+ });
183
+ }
184
+
185
+ function _checkSetfocusControl(fore, errors) {
186
+ fore.querySelectorAll('fx-setfocus[control]').forEach(el => {
187
+ const id = el.getAttribute('control');
188
+ if (_isDynamic(id)) return;
189
+ if (!_byId(fore, id)) {
190
+ errors.push({
191
+ element: el,
192
+ message: `<fx-setfocus control="${id}">: no element with id="${id}" found`,
193
+ });
194
+ }
195
+ });
196
+ }
197
+
198
+ function _checkToggleCase(fore, errors) {
199
+ fore.querySelectorAll('fx-toggle[case]').forEach(el => {
200
+ const id = el.getAttribute('case');
201
+ if (_isDynamic(id)) return;
202
+ if (!fore.querySelector(`fx-case#${id}`)) {
203
+ errors.push({
204
+ element: el,
205
+ message: `<fx-toggle case="${id}">: no <fx-case id="${id}"> found`,
206
+ });
207
+ }
208
+ });
209
+ }
210
+
211
+ /**
212
+ * Run all authoring checks on a given `<fx-fore>` element.
213
+ * Returns an array of `{ element, message }` error objects.
214
+ *
215
+ * @param {HTMLElement} fore
216
+ * @returns {{ element: HTMLElement, message: string }[]}
217
+ */
218
+ export function checkAuthoring(fore) {
219
+ const errors = [];
220
+ _checkSendSubmissions(fore, errors);
221
+ _checkDispatchTargets(fore, errors);
222
+ _checkXPathInstanceRefs(fore, errors);
223
+ _checkCallActions(fore, errors);
224
+ _checkShowHideDialogs(fore, errors);
225
+ _checkLoadAttachTo(fore, errors);
226
+ _checkRefreshControl(fore, errors);
227
+ _checkResetInstance(fore, errors);
228
+ _checkSetfocusControl(fore, errors);
229
+ _checkToggleCase(fore, errors);
230
+ return errors;
231
+ }
package/src/fore.js CHANGED
@@ -17,7 +17,7 @@ export class Fore {
17
17
 
18
18
  static REQUIRED_DEFAULT = false;
19
19
 
20
- static RELEVANT_DEFAULT = true
20
+ static RELEVANT_DEFAULT = true;
21
21
 
22
22
  static CONSTRAINT_DEFAULT = true;
23
23
 
@@ -267,7 +267,6 @@ export class Fore {
267
267
  return ['FX-BIND', 'FX-FUNCTION', 'FX-MODEL', 'FX-INSTANCE', 'FX-SUBMISSION'];
268
268
  }
269
269
 
270
-
271
270
  static async initUI(startElement) {
272
271
  const inited = new Promise(resolve => {
273
272
  const { children } = startElement;
@@ -293,82 +292,41 @@ export class Fore {
293
292
  * @returns {Promise<void>}
294
293
  */
295
294
  static async refreshChildren(startElement, force) {
296
- const refreshed = new Promise(resolve => {
297
- /*
298
- if there's an 'refresh-on-view' attribute the element wants to be handled by
299
- handleIntersect function that calls the refresh of the respective element and
300
- not the global one.
301
- */
302
- // if(!force && startElement.hasAttribute('refresh-on-view')) return;
303
-
304
- /* ### attempt with querySelectorAll is even slower than iterating recursively
305
-
306
- const children = startElement.querySelectorAll('[ref]');
307
- Array.from(children).forEach(uiElement => {
308
- if (Fore.isUiElement(uiElement.nodeName) && typeof uiElement.refresh === 'function') {
309
- uiElement.refresh();
310
- }
311
- });
312
- */
313
- const { children } = startElement;
314
- if (children) {
315
- for (const element of Array.from(children)) {
316
- if (element.nodeName.toUpperCase() === 'FX-FORE') {
317
- break;
318
- }
319
- if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
320
- /**
321
- * @type {import('./ForeElementMixin.js').default}
322
- */
323
- const bound = element;
324
- if (!force) {
325
- continue;
326
- }
327
- /*
328
- if(element.nodeName === 'FX-CASE') {
329
- console.log('hey - got a case', element);
330
- }
331
- */
332
- if (force === true) {
333
- // console.log('🔄 refreshing ', element);
334
- // Unconditional force refresh
335
- bound.refresh(force);
336
- continue;
337
- }
338
- if (typeof force !== 'object') {
339
- continue;
340
- }
341
- /*
342
- if (
343
- force.reason === 'index-function' &&
344
- bound.dependencies.isInvalidatedByIndexFunction()
345
- ) {
346
- console.log('🔄 refreshing ', element);
347
-
348
- bound.refresh(force);
349
- continue;
350
- }
351
-
352
- if (
353
- bound.dependencies.isInvalidatedByChildlistChanges(force.elementLocalnamesWithChanges)
354
- ) {
355
- console.log('🔄 refreshing ', element);
356
-
357
- bound.refresh(force);
358
- continue;
359
- }
360
- */
295
+ const children = startElement?.children ? Array.from(startElement.children) : [];
296
+ for (const element of children) {
297
+ // Do not cross into nested fore roots
298
+ if (element.nodeName.toUpperCase() === 'FX-FORE') {
299
+ break;
300
+ }
301
+
302
+ if (Fore.isUiElement(element.nodeName) && typeof element.refresh === 'function') {
303
+ /** @type {import('./ui/UIElement.js').UIElement} */
304
+ const bound = element;
305
+
306
+ // Keep old behavior: only refresh UI elements during full/forced refresh
307
+ // Any #refresh call does its own recursion.
308
+ if (force === true) {
309
+ const maybePromise = bound.refresh(force);
310
+ if (maybePromise && typeof maybePromise.then === 'function') {
311
+ await maybePromise;
361
312
  }
362
- if (!(element.inert === true)) {
363
- // testing for inert catches model and action elements and should just leave updateable html elements
364
- Fore.refreshChildren(element, force);
313
+ continue;
314
+ }
315
+ if (typeof force === 'object') {
316
+ // future selective refresh logic can live here if you re-enable it
317
+ const maybePromise = bound.refresh(force);
318
+ if (maybePromise && typeof maybePromise.then === 'function') {
319
+ await maybePromise;
365
320
  }
321
+ continue;
366
322
  }
367
323
  }
368
- resolve('done');
369
- });
370
324
 
371
- return refreshed;
325
+ // Traverse DOM unless inert
326
+ if (!(element.inert === true)) {
327
+ await Fore.refreshChildren(element, force);
328
+ }
329
+ }
372
330
  }
373
331
 
374
332
  static copyDom(inputElement) {
@@ -564,10 +522,7 @@ export class Fore {
564
522
  const reg = /(>)(<)(\/*)/g;
565
523
  const wsexp = / *(.*) +\n/g;
566
524
  const contexp = /(<.+>)(.+\n)/g;
567
- xml = xml
568
- .replace(reg, '$1\n$2$3')
569
- .replace(wsexp, '$1\n')
570
- .replace(contexp, '$1\n$2');
525
+ xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
571
526
  let formatted = '';
572
527
  const lines = xml.split('\n');
573
528
  let indent = 0;
@@ -1,8 +1,27 @@
1
1
  import { createTypedValueFactory, registerCustomXPathFunction } from 'fontoxpath';
2
2
  import { evaluateXPath, globallyDeclaredFunctionLocalNames } from '../xpath-evaluation.js';
3
3
 
4
+ // FontoXPath custom functions are registered globally. If multiple <fx-functionlib> (or multiple
5
+ // <fx-fore> instances) register the same function name+arity, later registrations would overwrite
6
+ // earlier ones across the whole page. We enforce "first wins".
7
+ const _registeredFunctionKeys = new Set();
8
+
9
+ function _makeFunctionKey(functionIdentifier, arity) {
10
+ if (typeof functionIdentifier === 'string') {
11
+ return `str:${functionIdentifier}#${arity}`;
12
+ }
13
+ return `{${functionIdentifier.namespaceURI}}${functionIdentifier.localName}#${arity}`;
14
+ }
15
+
16
+ function _ensureGlobalUnprefixedName(localName) {
17
+ // Keep this list unique to avoid unbounded growth across tests
18
+ if (!globallyDeclaredFunctionLocalNames.includes(localName)) {
19
+ globallyDeclaredFunctionLocalNames.push(localName);
20
+ }
21
+ }
22
+
4
23
  /**
5
- * @param functionObject {{signature: string, type: string|null, functionBody: string}}
24
+ * @param functionObject {{signature: string, type: string|null, functionBody: string, implementation?: Function}}
6
25
  * @param formElement {HTMLElement} The form element connected to this function. Used to determine inscope context
7
26
  * @returns {undefined}
8
27
  */
@@ -31,29 +50,51 @@ export default function registerFunction(functionObject, formElement) {
31
50
  ? { namespaceURI: 'http://www.w3.org/2005/xquery-local-functions', localName }
32
51
  : `${prefix}:${localName}`;
33
52
 
34
- // Make the function available globally w/o a prefix. See the functionNameResolver for for how
35
- // functionObject is picked up
36
- if (!prefix) {
37
- globallyDeclaredFunctionLocalNames.push(localName);
38
- }
39
-
40
53
  const paramParts = params
41
- ? params.split(',').map(param => {
42
- const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>[^\s]+))/);
43
- if (!match) {
44
- throw new Error(`Param ${param} could not be parsed`);
45
- }
46
- const { variableName, varType } = match.groups;
47
- return {
48
- variableName,
49
- variableType: varType || 'item()*',
50
- };
51
- })
54
+ ? params
55
+ .split(',')
56
+ .map(param => param.trim())
57
+ .filter(Boolean)
58
+ .map(param => {
59
+ const match = param.match(/(?<variableName>\$[^\s]+)(?:\sas\s(?<varType>[^\s]+))/);
60
+ if (!match) {
61
+ throw new Error(`Param ${param} could not be parsed`);
62
+ }
63
+ const { variableName, varType } = match.groups;
64
+ return {
65
+ variableName,
66
+ variableType: varType || 'item()*',
67
+ };
68
+ })
52
69
  : [];
53
70
 
71
+ const arity = paramParts.length;
72
+
73
+ // -------------------------------------------------
74
+ // FIRST-WINS GUARD (name + arity) WITH NAME EXPORT
75
+ // -------------------------------------------------
76
+ const key = _makeFunctionKey(functionIdentifier, arity);
77
+
78
+ if (_registeredFunctionKeys.has(key)) {
79
+ // If this registration is unprefixed, we must still make it callable without prefix.
80
+ // This fixes the unit test case where local:hello-world() was registered earlier and
81
+ // hello-world() should resolve to that implementation.
82
+ if (!prefix) {
83
+ _ensureGlobalUnprefixedName(localName);
84
+ }
85
+ return;
86
+ }
87
+
88
+ _registeredFunctionKeys.add(key);
89
+
90
+ // Make the function available globally w/o a prefix.
91
+ if (!prefix) {
92
+ _ensureGlobalUnprefixedName(localName);
93
+ }
94
+
54
95
  switch (type) {
55
96
  case 'text/javascript': {
56
- // NEW: if a real JS function is provided (module libs), register it directly.
97
+ // If a real JS function is provided (module libs), register it directly.
57
98
  if (typeof functionObject.implementation === 'function') {
58
99
  const impl = functionObject.implementation;
59
100
  registerCustomXPathFunction(
@@ -74,6 +115,7 @@ export default function registerFunction(functionObject, formElement) {
74
115
  'form',
75
116
  functionObject.functionBody,
76
117
  );
118
+
77
119
  registerCustomXPathFunction(
78
120
  functionIdentifier,
79
121
  paramParts.map(paramPart => paramPart.variableType),
@@ -83,25 +125,27 @@ export default function registerFunction(functionObject, formElement) {
83
125
  );
84
126
  break;
85
127
  }
128
+
86
129
  case 'text/xquf':
87
130
  case 'text/xquery':
88
131
  case 'text/xpath': {
89
132
  const typedValueFactories = paramParts.map(param =>
90
133
  createTypedValueFactory(param.variableType),
91
134
  );
135
+
92
136
  const language =
93
137
  type === 'text/xpath'
94
138
  ? 'XPath3.1'
95
139
  : type === 'text/xquery'
96
140
  ? 'XQuery3.1'
97
141
  : 'XQueryUpdate3.1';
142
+
98
143
  const fun = (domFacade, ...args) =>
99
144
  evaluateXPath(
100
145
  functionObject.functionBody,
101
146
  formElement.getInScopeContext(),
102
147
  formElement.getOwnerForm(),
103
148
  paramParts.reduce((variablesByName, paramPart, i) => {
104
- // Because we know the XPath type here (from the function declaration) we do not have to depend on the implicit typings
105
149
  variablesByName[paramPart.variableName.replace('$', '')] = typedValueFactories[i](
106
150
  args[i],
107
151
  domFacade,
@@ -110,6 +154,7 @@ export default function registerFunction(functionObject, formElement) {
110
154
  }, {}),
111
155
  { language },
112
156
  );
157
+
113
158
  registerCustomXPathFunction(
114
159
  functionIdentifier,
115
160
  paramParts.map(paramPart => paramPart.variableType),