bpmnlint-plugin-camunda-compat 2.59.1 → 2.59.2
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.
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ const { is } = require('bpmnlint-utils');
|
|
|
3
3
|
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
4
|
|
|
5
5
|
const { findExtensionElement, findAncestorAdHocSubProcess, isAgenticAdHocSubProcess } = require('../utils/element');
|
|
6
|
-
const { CORRECT_NAME, NAME_ALIASES, findFunctionInvocations,
|
|
6
|
+
const { CORRECT_NAME, NAME_ALIASES, findFunctionInvocations, getArgs } = require('./utils/feel');
|
|
7
7
|
const { reportErrors } = require('../utils/reporter');
|
|
8
8
|
const { ERROR_TYPES } = require('../utils/error-types');
|
|
9
9
|
const { skipInNonExecutableProcess } = require('../utils/rule');
|
|
@@ -34,6 +34,10 @@ const { annotateRule } = require('../helper');
|
|
|
34
34
|
*
|
|
35
35
|
* A description that is simply absent (no argument, or an empty string) is
|
|
36
36
|
* valid: the fromAi() description is optional, so neither rule reports it.
|
|
37
|
+
*
|
|
38
|
+
* Positional and named calls are equivalent, because
|
|
39
|
+
* FromAiTaggedParameterExtractor funnels both into the same five-argument
|
|
40
|
+
* call. Every check below therefore applies to both forms.
|
|
37
41
|
*/
|
|
38
42
|
// ─── Constraint validators ────────────────────────────────────────────────────
|
|
39
43
|
|
|
@@ -214,9 +218,9 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
214
218
|
continue;
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
const args =
|
|
221
|
+
const args = getArgs(inv.node, expr);
|
|
218
222
|
|
|
219
|
-
if (args
|
|
223
|
+
if (!args[0]) {
|
|
220
224
|
errors.push({
|
|
221
225
|
message: 'fromAi() requires a key argument: a FEEL path like toolCall.url.',
|
|
222
226
|
data: { type: ERROR_TYPES.AGENT_FEEL_KEY_MISSING },
|
|
@@ -229,7 +233,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
229
233
|
errors.push(keyError);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
if (args
|
|
236
|
+
if (args[1]) {
|
|
233
237
|
const descriptionError = validateDescriptionTypeInvalid(args[1]);
|
|
234
238
|
if (descriptionError) {
|
|
235
239
|
errors.push(descriptionError);
|
|
@@ -284,7 +288,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
284
288
|
|
|
285
289
|
const expr = source.substring(1).trim();
|
|
286
290
|
for (const inv of findFunctionInvocations(expr)) {
|
|
287
|
-
const args =
|
|
291
|
+
const args = getArgs(inv.node, expr);
|
|
288
292
|
const key = args[ 0 ];
|
|
289
293
|
if (key && key.type === 'PathExpression' && key.text.startsWith('toolCall.')) {
|
|
290
294
|
(keyOccurrences[ key.text ] = keyOccurrences[ key.text ] || []).push(input);
|
|
@@ -34,11 +34,18 @@ const isFeelProperty = (node, propertyName, value) => {
|
|
|
34
34
|
|
|
35
35
|
// ─── fromAi() lezer helpers ─────────────────────────────────────────────────
|
|
36
36
|
// Used by agent-fromai-contract to locate fromAi() calls and read their
|
|
37
|
-
//
|
|
37
|
+
// arguments, written either positionally or as named parameters.
|
|
38
38
|
|
|
39
39
|
const CORRECT_NAME = 'fromAi';
|
|
40
40
|
const NAME_ALIASES = [ 'fromai', 'fromAI' ];
|
|
41
41
|
|
|
42
|
+
// fromAi() signature, mirroring FromAiTaggedParameterExtractor in
|
|
43
|
+
// camunda/camunda: it normalizes positional and named calls onto these same
|
|
44
|
+
// five slots, so every downstream check reads one index for both call forms.
|
|
45
|
+
// The list is also what tells a name the engine recognizes from a typo like
|
|
46
|
+
// "valu", which the engine drops.
|
|
47
|
+
const PARAMETER_NAMES = [ 'value', 'description', 'type', 'schema', 'options' ];
|
|
48
|
+
|
|
42
49
|
function findFunctionInvocations(expr) {
|
|
43
50
|
const tree = parser.parse(expr);
|
|
44
51
|
const result = [];
|
|
@@ -65,18 +72,64 @@ function findFunctionInvocations(expr) {
|
|
|
65
72
|
return result;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
// Comments and error nodes are siblings of the real arguments, so reading them
|
|
76
|
+
// as arguments would report a comment as the wrong key type, or an incomplete
|
|
77
|
+
// call as a "⚠" one.
|
|
78
|
+
const nextArgNode = node => {
|
|
79
|
+
while (node && node.type.isSkipped) {
|
|
80
|
+
node = node.nextSibling;
|
|
81
|
+
}
|
|
82
|
+
return node;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Reads a fromAi() call's arguments into an array indexed by PARAMETER_NAMES.
|
|
87
|
+
*
|
|
88
|
+
* A named call fills only the slots it mentions, so the array is sparse: for
|
|
89
|
+
* `fromAi(type: "number")` slot 2 is set while slot 0 is undefined. Callers
|
|
90
|
+
* must test individual slots for presence rather than read `args.length`.
|
|
91
|
+
*/
|
|
92
|
+
function getArgs(invocationNode, expr) {
|
|
93
|
+
const toArg = node => ({ type: node.type.name, text: expr.slice(node.from, node.to) });
|
|
94
|
+
|
|
69
95
|
let child = invocationNode.firstChild;
|
|
70
96
|
while (child) {
|
|
71
97
|
if (child.type.name === 'PositionalParameters') {
|
|
72
98
|
const args = [];
|
|
73
|
-
let arg = child.firstChild;
|
|
99
|
+
let arg = nextArgNode(child.firstChild);
|
|
74
100
|
while (arg) {
|
|
75
|
-
args.push(
|
|
76
|
-
arg = arg.nextSibling;
|
|
101
|
+
args.push(toArg(arg));
|
|
102
|
+
arg = nextArgNode(arg.nextSibling);
|
|
77
103
|
}
|
|
78
104
|
return args;
|
|
79
105
|
}
|
|
106
|
+
|
|
107
|
+
if (child.type.name === 'NamedParameters') {
|
|
108
|
+
|
|
109
|
+
// Named arguments resolve by name, and their order in the call carries no
|
|
110
|
+
// meaning, so each one goes to its own slot. A name outside the signature
|
|
111
|
+
// is dropped, matching the engine, which reads the parameter map by name
|
|
112
|
+
// and never sees it.
|
|
113
|
+
const args = [];
|
|
114
|
+
let param = child.firstChild;
|
|
115
|
+
while (param) {
|
|
116
|
+
if (param.type.name === 'NamedParameter') {
|
|
117
|
+
const nameNode = param.firstChild;
|
|
118
|
+
const valueNode = nameNode && nextArgNode(nameNode.nextSibling);
|
|
119
|
+
|
|
120
|
+
if (nameNode && nameNode.type.name === 'ParameterName' && valueNode) {
|
|
121
|
+
const index = PARAMETER_NAMES.indexOf(expr.slice(nameNode.from, nameNode.to));
|
|
122
|
+
|
|
123
|
+
if (index !== -1) {
|
|
124
|
+
args[ index ] = toArg(valueNode);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
param = param.nextSibling;
|
|
129
|
+
}
|
|
130
|
+
return args;
|
|
131
|
+
}
|
|
132
|
+
|
|
80
133
|
child = child.nextSibling;
|
|
81
134
|
}
|
|
82
135
|
return [];
|
|
@@ -87,5 +140,5 @@ module.exports = {
|
|
|
87
140
|
CORRECT_NAME,
|
|
88
141
|
NAME_ALIASES,
|
|
89
142
|
findFunctionInvocations,
|
|
90
|
-
|
|
143
|
+
getArgs
|
|
91
144
|
};
|