bpmnlint-plugin-camunda-compat 2.58.2 → 2.59.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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/rules/camunda-cloud/agent-fromai-contract.js +18 -20
- package/rules/camunda-cloud/agent-tool-documentation.js +1 -1
- package/rules/camunda-cloud/agent-tool-output-key.js +42 -10
- package/rules/camunda-cloud/before-all-execution-listener.js +2 -2
- package/rules/camunda-cloud/connector-properties/config.js +2 -2
- package/rules/camunda-cloud/error-reference.js +10 -1
- package/rules/camunda-cloud/escalation-reference.js +10 -1
- package/rules/camunda-cloud/feel-compatibility.js +2 -4
- package/rules/camunda-cloud/feel.js +2 -4
- package/rules/camunda-cloud/link-event.js +2 -4
- package/rules/camunda-cloud/message-reference.js +10 -1
- package/rules/camunda-cloud/no-before-all-execution-listener.js +2 -2
- package/rules/camunda-cloud/no-expression.js +2 -4
- package/rules/camunda-cloud/no-task-schedule.js +25 -0
- package/rules/camunda-cloud/no-zeebe-properties.js +18 -0
- package/rules/camunda-cloud/secrets.js +2 -4
- package/rules/camunda-cloud/signal-reference.js +10 -1
- package/rules/camunda-cloud/subscription.js +10 -1
- package/rules/utils/element.js +73 -27
package/README.md
CHANGED
|
@@ -79,7 +79,8 @@ npx bpmnlint my-diagram.bpmn
|
|
|
79
79
|
## Resources
|
|
80
80
|
|
|
81
81
|
* [Issues](https://github.com/camunda/bpmnlint-plugin-camunda-compat/issues)
|
|
82
|
-
|
|
82
|
+
* [Architecture documentation](./docs/ARCHITECTURE.md)
|
|
83
|
+
* [Rule authoring guide](./docs/RULE_AUTHORING.md)
|
|
83
84
|
|
|
84
85
|
## Related
|
|
85
86
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const { is } = require('bpmnlint-utils');
|
|
2
2
|
|
|
3
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
|
+
|
|
3
5
|
const { findExtensionElement, findAncestorAdHocSubProcess, isAgenticAdHocSubProcess } = require('../utils/element');
|
|
4
6
|
const { CORRECT_NAME, NAME_ALIASES, findFunctionInvocations, getPositionalArgs } = require('./utils/feel');
|
|
5
7
|
const { reportErrors } = require('../utils/reporter');
|
|
@@ -156,12 +158,11 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
};
|
|
161
|
+
// Moddle path to this input parameter's source, so a single downstream
|
|
162
|
+
// resolver maps it to the entry the modeler actually renders — the standard
|
|
163
|
+
// input mapping, or a template's custom entry. The rule stays agnostic to
|
|
164
|
+
// the id scheme.
|
|
165
|
+
const path = pathConcat(getPath(node, task), 'source');
|
|
165
166
|
|
|
166
167
|
const ahsp = findAncestorAdHocSubProcess(task);
|
|
167
168
|
|
|
@@ -169,7 +170,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
169
170
|
reportErrors(task, reporter, invocations.map(() => ({
|
|
170
171
|
message: 'fromAi() should only be used inside an agentic sub-process.',
|
|
171
172
|
data: { type: ERROR_TYPES.AGENT_FEEL_WRONG_CONTEXT },
|
|
172
|
-
|
|
173
|
+
path,
|
|
173
174
|
})));
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
@@ -179,7 +180,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
179
180
|
reportErrors(task, reporter, invocations.map(() => ({
|
|
180
181
|
message: `The "${ahspLabel}" sub-process is not marked as agentic, so fromAi() has no effect.`,
|
|
181
182
|
data: { type: ERROR_TYPES.AGENT_FEEL_WRONG_CONTEXT },
|
|
182
|
-
|
|
183
|
+
path,
|
|
183
184
|
})));
|
|
184
185
|
return;
|
|
185
186
|
}
|
|
@@ -194,7 +195,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
194
195
|
reportErrors(task, reporter, invocations.map(() => ({
|
|
195
196
|
message: 'fromAi() is ignored here: only the tool\'s entry element defines AI inputs. Define it there and read the toolCall variable directly.',
|
|
196
197
|
data: { type: ERROR_TYPES.AGENT_FEEL_NON_ENTRY_ELEMENT },
|
|
197
|
-
|
|
198
|
+
path,
|
|
198
199
|
})));
|
|
199
200
|
return;
|
|
200
201
|
}
|
|
@@ -237,7 +238,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
if (errors.length) {
|
|
240
|
-
reportErrors(task, reporter, errors.map(error => ({ ...error,
|
|
241
|
+
reportErrors(task, reporter, errors.map(error => ({ ...error, path })));
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
@@ -273,7 +274,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
273
274
|
return;
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
const
|
|
277
|
+
const keyOccurrences = {};
|
|
277
278
|
|
|
278
279
|
for (const input of ioMapping.get('inputParameters') || []) {
|
|
279
280
|
const source = input.get('source');
|
|
@@ -286,18 +287,18 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
286
287
|
const args = getPositionalArgs(inv.node, expr);
|
|
287
288
|
const key = args[ 0 ];
|
|
288
289
|
if (key && key.type === 'PathExpression' && key.text.startsWith('toolCall.')) {
|
|
289
|
-
|
|
290
|
+
(keyOccurrences[ key.text ] = keyOccurrences[ key.text ] || []).push(input);
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
const duplicates = Object.keys(
|
|
295
|
+
const duplicates = Object.keys(keyOccurrences).filter(key => keyOccurrences[ key ].length > 1);
|
|
295
296
|
|
|
296
297
|
if (duplicates.length) {
|
|
297
298
|
reportErrors(task, reporter, duplicates.map(key => ({
|
|
298
299
|
message: `fromAi() key ${ key } is declared more than once in this tool. Declare it once and reference it directly elsewhere.`,
|
|
299
300
|
data: { type: ERROR_TYPES.AGENT_FEEL_KEY_DUPLICATE },
|
|
300
|
-
|
|
301
|
+
paths: keyOccurrences[ key ].map(input => pathConcat(getPath(input, task), 'source')),
|
|
301
302
|
})));
|
|
302
303
|
}
|
|
303
304
|
}
|
|
@@ -330,15 +331,12 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
330
331
|
return;
|
|
331
332
|
}
|
|
332
333
|
|
|
333
|
-
const
|
|
334
|
-
const propertiesPanel = {
|
|
335
|
-
entryIds: [ `${ task.get('id') }-output-${ outputIndex }-source` ]
|
|
336
|
-
};
|
|
334
|
+
const path = pathConcat(getPath(node, task), 'source');
|
|
337
335
|
|
|
338
336
|
reportErrors(task, reporter, invocations.map(() => ({
|
|
339
337
|
message: 'fromAi() defines a tool input and has no effect in an output mapping. Define it in an input mapping on the tool\'s entry element.',
|
|
340
338
|
data: { type: ERROR_TYPES.AGENT_FEEL_WRONG_CONTEXT },
|
|
341
|
-
|
|
339
|
+
path,
|
|
342
340
|
})));
|
|
343
341
|
}
|
|
344
342
|
|
|
@@ -368,7 +366,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
368
366
|
reportErrors(node, reporter, invocations.map(() => ({
|
|
369
367
|
message: 'fromAi() defines a tool input and cannot be used in a sequence flow condition. Define it in an input mapping on the tool\'s entry element.',
|
|
370
368
|
data: { type: ERROR_TYPES.AGENT_FEEL_WRONG_CONTEXT },
|
|
371
|
-
|
|
369
|
+
path: getPath(condition, node),
|
|
372
370
|
})));
|
|
373
371
|
}
|
|
374
372
|
});
|
|
@@ -29,7 +29,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
29
29
|
{
|
|
30
30
|
message: 'Tool documentation is missing.',
|
|
31
31
|
data: { type: ERROR_TYPES.AGENT_TOOL_DOCUMENTATION_MISSING },
|
|
32
|
-
|
|
32
|
+
path: [ 'documentation' ],
|
|
33
33
|
},
|
|
34
34
|
]);
|
|
35
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const { is } = require('bpmnlint-utils');
|
|
2
2
|
|
|
3
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
|
+
|
|
3
5
|
const { isAgenticToolElement } = require('../utils/element');
|
|
4
6
|
const { reportErrors, getName } = require('../utils/reporter');
|
|
5
7
|
const { ERROR_TYPES } = require('../utils/error-types');
|
|
@@ -38,7 +40,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
38
40
|
reportErrors(node, reporter, {
|
|
39
41
|
message: 'Tool returns nothing to the agent. Set a "toolCallResult" (at minimum, note the task completed).',
|
|
40
42
|
data: { type: ERROR_TYPES.AGENT_TOOL_RESULT_MISSING },
|
|
41
|
-
|
|
43
|
+
path: getOutputsPath(node),
|
|
42
44
|
});
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
@@ -51,7 +53,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
51
53
|
reportErrors(casingMismatch.element, reporter, {
|
|
52
54
|
message: `Wrong casing "${getCasingMismatchText(casingMismatch)}": use toolCallResult (case-sensitive).`,
|
|
53
55
|
data: { type: ERROR_TYPES.AGENT_TOOL_OUTPUT_KEY_CASING_INVALID },
|
|
54
|
-
|
|
56
|
+
path: getChannelPath(casingMismatch),
|
|
55
57
|
});
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
@@ -63,7 +65,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
63
65
|
reportErrors(misdirected.element, reporter, {
|
|
64
66
|
message: '"toolCallResult" output is not mapped.',
|
|
65
67
|
data: { type: ERROR_TYPES.AGENT_TOOL_OUTPUT_KEY_INVALID },
|
|
66
|
-
|
|
68
|
+
path: getChannelPath(misdirected),
|
|
67
69
|
});
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
@@ -96,7 +98,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
96
98
|
reportErrors(overwriter.element, reporter, {
|
|
97
99
|
message: `This overwrites the "toolCallResult" value set on "${overwrittenLabel}".`,
|
|
98
100
|
data: { type: ERROR_TYPES.AGENT_TOOL_OUTPUT_KEY_OVERWRITE },
|
|
99
|
-
|
|
101
|
+
path: getChannelPath(overwriter),
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -120,8 +122,9 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
120
122
|
*
|
|
121
123
|
* @param {ModdleElement} entry
|
|
122
124
|
*
|
|
123
|
-
* @returns {Object} { channels, linear } — channels as { kind, value, element
|
|
124
|
-
* (element being whichever element in the flow actually wrote
|
|
125
|
+
* @returns {Object} { channels, linear } — channels as { kind, value, element,
|
|
126
|
+
* node, property } (element being whichever element in the flow actually wrote
|
|
127
|
+
* this channel; node/property the moddle leaf that carries the offending value),
|
|
125
128
|
* and linear being true when the flow is a single non-branching chain
|
|
126
129
|
*/
|
|
127
130
|
function collectResultChannels(entry) {
|
|
@@ -191,14 +194,14 @@ function collectElementChannels(element, channels) {
|
|
|
191
194
|
for (const value of extensionElements.get('values')) {
|
|
192
195
|
if (is(value, 'zeebe:IoMapping')) {
|
|
193
196
|
for (const output of value.get('outputParameters') || []) {
|
|
194
|
-
channels.push({ kind: 'output', value: output.get('target') || '', source: output.get('source'), element });
|
|
197
|
+
channels.push({ kind: 'output', value: output.get('target') || '', source: output.get('source'), element, node: output, property: 'target' });
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
if (is(value, 'zeebe:Script') || is(value, 'zeebe:CalledDecision')) {
|
|
199
202
|
const resultVariable = value.get('resultVariable');
|
|
200
203
|
if (resultVariable) {
|
|
201
|
-
channels.push({ kind: 'resultVariable', value: resultVariable, element });
|
|
204
|
+
channels.push({ kind: 'resultVariable', value: resultVariable, element, node: value, property: 'resultVariable' });
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
|
|
@@ -212,16 +215,45 @@ function collectElementChannels(element, channels) {
|
|
|
212
215
|
for (const header of value.get('values') || []) {
|
|
213
216
|
const key = header.get('key');
|
|
214
217
|
if (key === 'resultVariable') {
|
|
215
|
-
channels.push({ kind: 'resultVariable', value: header.get('value') || '', element });
|
|
218
|
+
channels.push({ kind: 'resultVariable', value: header.get('value') || '', element, node: header, property: 'value' });
|
|
216
219
|
}
|
|
217
220
|
if (key === 'resultExpression') {
|
|
218
|
-
channels.push({ kind: 'resultExpression', value: header.get('value') || '', element });
|
|
221
|
+
channels.push({ kind: 'resultExpression', value: header.get('value') || '', element, node: header, property: 'value' });
|
|
219
222
|
}
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
225
|
}
|
|
223
226
|
}
|
|
224
227
|
|
|
228
|
+
// The moddle leaf path to the write that actually carries the offending value
|
|
229
|
+
// (an output target, a script/decision resultVariable, a connector header
|
|
230
|
+
// value), relative to the element the finding is reported on. The panel
|
|
231
|
+
// resolves this render-agnostically to the concrete field — a standard output
|
|
232
|
+
// entry, or the matching element-template field when the tool is template
|
|
233
|
+
// bound. Degrades to null (element selection) if the node is detached.
|
|
234
|
+
function getChannelPath(channel) {
|
|
235
|
+
return pathConcat(getPath(channel.node, channel.element), channel.property);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Best-effort location for a "tool returns nothing" finding: the tool's output
|
|
239
|
+
// mapping collection, which the panel resolves outward to the outputs group.
|
|
240
|
+
// When the tool has no output mapping at all there is nothing to point at, so
|
|
241
|
+
// the finding degrades to element selection (null path) rather than fabricate a
|
|
242
|
+
// target.
|
|
243
|
+
function getOutputsPath(element) {
|
|
244
|
+
const extensionElements = element.get('extensionElements');
|
|
245
|
+
if (!extensionElements) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const ioMapping = (extensionElements.get('values') || []).find(value => is(value, 'zeebe:IoMapping'));
|
|
250
|
+
if (!ioMapping) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return pathConcat(getPath(ioMapping, element), 'outputParameters');
|
|
255
|
+
}
|
|
256
|
+
|
|
225
257
|
function isToolCallResultChannel({ kind, value }) {
|
|
226
258
|
if (kind === 'resultExpression') {
|
|
227
259
|
return /\btoolCallResult\b/.test(value);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { is } = require('bpmnlint-utils');
|
|
2
2
|
|
|
3
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
3
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
4
|
|
|
5
5
|
const {
|
|
6
6
|
ERROR_TYPES,
|
|
@@ -33,7 +33,7 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
33
33
|
return [
|
|
34
34
|
{
|
|
35
35
|
message: 'Property <eventType> of <zeebe:ExecutionListener> with value <beforeAll> only allowed on multi-instance elements',
|
|
36
|
-
path:
|
|
36
|
+
path: pathConcat(getPath(listener, node), 'eventType'),
|
|
37
37
|
data: {
|
|
38
38
|
type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
|
|
39
39
|
node: listener,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
1
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
2
2
|
|
|
3
3
|
const { findExtensionElement } = require('../../utils/element');
|
|
4
4
|
|
|
@@ -75,7 +75,7 @@ function getInboundConnectorError(node, propertyName, allowedVersion) {
|
|
|
75
75
|
|
|
76
76
|
return {
|
|
77
77
|
message: `Connector property <name> with value <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer.`,
|
|
78
|
-
path: path
|
|
78
|
+
path: pathConcat(path || [], 'name'),
|
|
79
79
|
data: {
|
|
80
80
|
type: ERROR_TYPES.CONNECTORS_PROPERTY_VALUE_NOT_ALLOWED,
|
|
81
81
|
node: property,
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
getEventDefinition,
|
|
8
|
+
getReferencePath,
|
|
8
9
|
hasProperties
|
|
9
10
|
} = require('../utils/element');
|
|
10
11
|
|
|
@@ -52,11 +53,19 @@ module.exports = skipInNonExecutableProcess(function({ version }) {
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
const nodePath = getReferencePath({
|
|
57
|
+
element: node,
|
|
58
|
+
referenceHolder: eventDefinition,
|
|
59
|
+
referenceProperty: 'errorRef',
|
|
60
|
+
referencedRoot: errorRef,
|
|
61
|
+
node: errorRef
|
|
62
|
+
});
|
|
63
|
+
|
|
55
64
|
errors = hasProperties(errorRef, {
|
|
56
65
|
errorCode: {
|
|
57
66
|
required: true
|
|
58
67
|
}
|
|
59
|
-
}, node);
|
|
68
|
+
}, node, nodePath);
|
|
60
69
|
|
|
61
70
|
if (errors.length) {
|
|
62
71
|
reportErrors(node, reporter, errors);
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
getEventDefinition,
|
|
8
|
+
getReferencePath,
|
|
8
9
|
hasProperties
|
|
9
10
|
} = require('../utils/element');
|
|
10
11
|
|
|
@@ -47,11 +48,19 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
const nodePath = getReferencePath({
|
|
52
|
+
element: node,
|
|
53
|
+
referenceHolder: eventDefinition,
|
|
54
|
+
referenceProperty: 'escalationRef',
|
|
55
|
+
referencedRoot: escalationRef,
|
|
56
|
+
node: escalationRef
|
|
57
|
+
});
|
|
58
|
+
|
|
50
59
|
errors = hasProperties(escalationRef, {
|
|
51
60
|
escalationCode: {
|
|
52
61
|
required: true
|
|
53
62
|
}
|
|
54
|
-
}, node);
|
|
63
|
+
}, node, nodePath);
|
|
55
64
|
|
|
56
65
|
if (errors.length) {
|
|
57
66
|
reportErrors(node, reporter, errors);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { is } = require('bpmnlint-utils');
|
|
2
2
|
|
|
3
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
3
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
4
|
const { camundaBuiltins, camundaReservedNameBuiltins } = require('@camunda/feel-builtins');
|
|
5
5
|
|
|
6
6
|
const { FeelAnalyzer } = require('@bpmn-io/feel-analyzer');
|
|
@@ -77,9 +77,7 @@ module.exports = skipInNonExecutableProcess(function(config = {}) {
|
|
|
77
77
|
|
|
78
78
|
errors.push({
|
|
79
79
|
message: `FEEL function <${ builtin.name }> requires Camunda ${ builtin.engines.camunda }`,
|
|
80
|
-
path: path
|
|
81
|
-
? [ ...path, propertyName ]
|
|
82
|
-
: [ propertyName ],
|
|
80
|
+
path: pathConcat(path || [], propertyName),
|
|
83
81
|
data: {
|
|
84
82
|
type: ERROR_TYPES.FEEL_EXPRESSION_UNSUPPORTED_FUNCTION,
|
|
85
83
|
node,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { is } = require('bpmnlint-utils');
|
|
2
2
|
|
|
3
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
3
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
4
4
|
|
|
5
5
|
const { FeelAnalyzer } = require('@bpmn-io/feel-analyzer');
|
|
6
6
|
|
|
@@ -52,9 +52,7 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
52
52
|
|
|
53
53
|
errors.push({
|
|
54
54
|
message: `Property <${ propertyName }> is not a valid FEEL expression`,
|
|
55
|
-
path: path
|
|
56
|
-
? [ ...path, propertyName ]
|
|
57
|
-
: [ propertyName ],
|
|
55
|
+
path: pathConcat(path || [], propertyName),
|
|
58
56
|
data: {
|
|
59
57
|
type: ERROR_TYPES.FEEL_EXPRESSION_INVALID,
|
|
60
58
|
node,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
1
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
is,
|
|
@@ -72,9 +72,7 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
72
72
|
|
|
73
73
|
reportErrors(linkCatchEvent, reporter, {
|
|
74
74
|
message: `Property of type <bpmn:LinkEventDefinition> has property <name> with duplicate value of <${ name }>`,
|
|
75
|
-
path: path
|
|
76
|
-
? [ ...path, 'name' ]
|
|
77
|
-
: [ 'name' ],
|
|
75
|
+
path: pathConcat(path || [], 'name'),
|
|
78
76
|
data: {
|
|
79
77
|
type: ERROR_TYPES.ELEMENT_PROPERTY_VALUE_DUPLICATED,
|
|
80
78
|
node: linkEventDefinition,
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
getEventDefinition,
|
|
8
|
+
getReferencePath,
|
|
8
9
|
hasProperties
|
|
9
10
|
} = require('../utils/element');
|
|
10
11
|
|
|
@@ -45,11 +46,19 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
45
46
|
|
|
46
47
|
const messageRef = eventDefinitionOrReceiveTask.get('messageRef');
|
|
47
48
|
|
|
49
|
+
const nodePath = getReferencePath({
|
|
50
|
+
element: node,
|
|
51
|
+
referenceHolder: eventDefinitionOrReceiveTask,
|
|
52
|
+
referenceProperty: 'messageRef',
|
|
53
|
+
referencedRoot: messageRef,
|
|
54
|
+
node: messageRef
|
|
55
|
+
});
|
|
56
|
+
|
|
48
57
|
errors = hasProperties(messageRef, {
|
|
49
58
|
name: {
|
|
50
59
|
required: true
|
|
51
60
|
}
|
|
52
|
-
}, node);
|
|
61
|
+
}, node, nodePath);
|
|
53
62
|
|
|
54
63
|
if (errors && errors.length) {
|
|
55
64
|
reportErrors(node, reporter, errors);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
1
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
findExtensionElement
|
|
@@ -30,7 +30,7 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
30
30
|
return [
|
|
31
31
|
{
|
|
32
32
|
message: `Property <eventType> of <zeebe:ExecutionListener> with value <beforeAll> only allowed by Camunda ${ ALLOWED_VERSION } or newer`,
|
|
33
|
-
path:
|
|
33
|
+
path: pathConcat(getPath(listener, node), 'eventType'),
|
|
34
34
|
data: {
|
|
35
35
|
type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
|
|
36
36
|
node: listener,
|
|
@@ -2,7 +2,7 @@ const {
|
|
|
2
2
|
is
|
|
3
3
|
} = require('bpmnlint-utils');
|
|
4
4
|
|
|
5
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
5
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
ERROR_TYPES,
|
|
@@ -103,9 +103,7 @@ function noExpression(node, propertyName, parentNode, allowedVersion) {
|
|
|
103
103
|
|
|
104
104
|
return {
|
|
105
105
|
message,
|
|
106
|
-
path: path
|
|
107
|
-
? [ ...path, propertyName ]
|
|
108
|
-
: [ propertyName ],
|
|
106
|
+
path: pathConcat(path || [], propertyName),
|
|
109
107
|
data
|
|
110
108
|
};
|
|
111
109
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
2
|
+
|
|
1
3
|
const { hasNoExtensionElement } = require('../utils/element');
|
|
2
4
|
|
|
3
5
|
const { reportErrors } = require('../utils/reporter');
|
|
@@ -9,6 +11,29 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
9
11
|
const errors = hasNoExtensionElement(node, 'zeebe:TaskSchedule', node, '8.2');
|
|
10
12
|
|
|
11
13
|
if (errors && errors.length) {
|
|
14
|
+
|
|
15
|
+
// add one leaf path per offending schedule field that is set, so consumers
|
|
16
|
+
// resolve entry ids render-agnostically
|
|
17
|
+
errors.forEach(error => {
|
|
18
|
+
const { extensionElement: taskSchedule } = error.data;
|
|
19
|
+
|
|
20
|
+
const path = getPath(taskSchedule, node);
|
|
21
|
+
|
|
22
|
+
const paths = [];
|
|
23
|
+
|
|
24
|
+
if (path && taskSchedule.get('dueDate')) {
|
|
25
|
+
paths.push(pathConcat(path, 'dueDate'));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (path && taskSchedule.get('followUpDate')) {
|
|
29
|
+
paths.push(pathConcat(path, 'followUpDate'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (paths.length) {
|
|
33
|
+
error.paths = paths;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
12
37
|
reportErrors(node, reporter, errors);
|
|
13
38
|
}
|
|
14
39
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
2
|
+
|
|
1
3
|
const { hasNoExtensionElement } = require('../utils/element');
|
|
2
4
|
|
|
3
5
|
const { reportErrors } = require('../utils/reporter');
|
|
@@ -9,6 +11,22 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
9
11
|
const errors = hasNoExtensionElement(node, 'zeebe:Properties', node, '8.1');
|
|
10
12
|
|
|
11
13
|
if (errors && errors.length) {
|
|
14
|
+
|
|
15
|
+
// add one leaf path per offending property `name` field, so consumers
|
|
16
|
+
// resolve entry ids render-agnostically
|
|
17
|
+
errors.forEach(error => {
|
|
18
|
+
const { extensionElement } = error.data;
|
|
19
|
+
|
|
20
|
+
const paths = extensionElement.get('properties')
|
|
21
|
+
.map(property => getPath(property, node))
|
|
22
|
+
.filter(path => path)
|
|
23
|
+
.map(path => pathConcat(path, 'name'));
|
|
24
|
+
|
|
25
|
+
if (paths.length) {
|
|
26
|
+
error.paths = paths;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
12
30
|
reportErrors(node, reporter, errors);
|
|
13
31
|
}
|
|
14
32
|
}
|
|
@@ -2,7 +2,7 @@ const { isString } = require('min-dash');
|
|
|
2
2
|
|
|
3
3
|
const { is } = require('bpmnlint-utils');
|
|
4
4
|
|
|
5
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
5
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
findExtensionElement,
|
|
@@ -99,9 +99,7 @@ function getReport(propertyName, node, parentNode) {
|
|
|
99
99
|
|
|
100
100
|
return {
|
|
101
101
|
message: `Property <${ propertyName }> uses deprecated secret expression format`,
|
|
102
|
-
path: path
|
|
103
|
-
? [ ...getPath(node, parentNode), propertyName ]
|
|
104
|
-
: [ propertyName ],
|
|
102
|
+
path: pathConcat(path || [], propertyName),
|
|
105
103
|
data: {
|
|
106
104
|
type: ERROR_TYPES.SECRET_EXPRESSION_FORMAT_DEPRECATED,
|
|
107
105
|
node,
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
getEventDefinition,
|
|
8
|
+
getReferencePath,
|
|
8
9
|
hasProperties
|
|
9
10
|
} = require('../utils/element');
|
|
10
11
|
|
|
@@ -48,11 +49,19 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
const nodePath = getReferencePath({
|
|
53
|
+
element: node,
|
|
54
|
+
referenceHolder: eventDefinition,
|
|
55
|
+
referenceProperty: 'signalRef',
|
|
56
|
+
referencedRoot: signalRef,
|
|
57
|
+
node: signalRef
|
|
58
|
+
});
|
|
59
|
+
|
|
51
60
|
errors = hasProperties(signalRef, {
|
|
52
61
|
name: {
|
|
53
62
|
required: true
|
|
54
63
|
}
|
|
55
|
-
}, node);
|
|
64
|
+
}, node, nodePath);
|
|
56
65
|
|
|
57
66
|
if (errors.length) {
|
|
58
67
|
reportErrors(node, reporter, errors);
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
const {
|
|
7
7
|
findExtensionElement,
|
|
8
8
|
getEventDefinition,
|
|
9
|
+
getReferencePath,
|
|
9
10
|
hasExtensionElement,
|
|
10
11
|
hasProperties
|
|
11
12
|
} = require('../utils/element');
|
|
@@ -49,11 +50,19 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
|
49
50
|
|
|
50
51
|
const subscription = findExtensionElement(messageRef, 'zeebe:Subscription');
|
|
51
52
|
|
|
53
|
+
const nodePath = getReferencePath({
|
|
54
|
+
element: node,
|
|
55
|
+
referenceHolder: eventDefinitionOrReceiveTask,
|
|
56
|
+
referenceProperty: 'messageRef',
|
|
57
|
+
referencedRoot: messageRef,
|
|
58
|
+
node: subscription
|
|
59
|
+
});
|
|
60
|
+
|
|
52
61
|
errors = hasProperties(subscription, {
|
|
53
62
|
correlationKey: {
|
|
54
63
|
required: true
|
|
55
64
|
}
|
|
56
|
-
}, node);
|
|
65
|
+
}, node, nodePath);
|
|
57
66
|
|
|
58
67
|
if (errors && errors.length) {
|
|
59
68
|
reportErrors(node, reporter, errors);
|
package/rules/utils/element.js
CHANGED
|
@@ -16,7 +16,7 @@ const {
|
|
|
16
16
|
isAny
|
|
17
17
|
} = require('bpmnlint-utils');
|
|
18
18
|
|
|
19
|
-
const { getPath } = require('@bpmn-io/moddle-utils');
|
|
19
|
+
const { getPath, pathConcat } = require('@bpmn-io/moddle-utils');
|
|
20
20
|
|
|
21
21
|
const { ERROR_TYPES } = require('./error-types');
|
|
22
22
|
|
|
@@ -24,6 +24,59 @@ const { greaterOrEqual } = require('./version');
|
|
|
24
24
|
|
|
25
25
|
module.exports.ERROR_TYPES = ERROR_TYPES;
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Build the `{ paths }` fragment for a multi-field finding: one leaf moddle path
|
|
29
|
+
* per offending node, rooted at `parentNode`. Nodes whose path can't be resolved
|
|
30
|
+
* are dropped; the fragment is empty when none resolve, so the `paths` key is
|
|
31
|
+
* only present when it carries usable locations.
|
|
32
|
+
*
|
|
33
|
+
* @param {Array<Object>} nodes
|
|
34
|
+
* @param {string} propertyName leaf property appended to each node's path
|
|
35
|
+
* @param {Object} [parentNode]
|
|
36
|
+
*
|
|
37
|
+
* @returns {{ paths?: Array<Array<string|number>> }}
|
|
38
|
+
*/
|
|
39
|
+
function leafPaths(nodes, propertyName, parentNode = null) {
|
|
40
|
+
const paths = nodes
|
|
41
|
+
.map(node => getPath(node, parentNode))
|
|
42
|
+
.filter(path => isArray(path))
|
|
43
|
+
.map(path => pathConcat(path, propertyName));
|
|
44
|
+
|
|
45
|
+
return paths.length ? { paths } : {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build a moddle path that locates `node` relative to `element` by following a
|
|
50
|
+
* single reference edge.
|
|
51
|
+
*
|
|
52
|
+
* `getPath` (from @bpmn-io/moddle-utils) only walks containment (`$parent`), so
|
|
53
|
+
* it roots any referenced node at `bpmn:Definitions` — a path that cannot be
|
|
54
|
+
* resolved starting from `element`. This helper instead stitches together
|
|
55
|
+
*
|
|
56
|
+
* getPath(referenceHolder, element) + referenceProperty + getPath(node, referencedRoot)
|
|
57
|
+
*
|
|
58
|
+
* yielding a path that resolves locally from `element`, because moddle
|
|
59
|
+
* `get(referenceProperty)` transparently follows the reference.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} options
|
|
62
|
+
* @param {Object} options.element root element the path is relative to
|
|
63
|
+
* @param {Object} options.referenceHolder node holding the reference (contained in `element`)
|
|
64
|
+
* @param {string} options.referenceProperty name of the reference property
|
|
65
|
+
* @param {Object} options.referencedRoot referenced root element
|
|
66
|
+
* @param {Object} options.node checked node (contained in `referencedRoot`)
|
|
67
|
+
*
|
|
68
|
+
* @returns {Array<string|number>|null} null when a segment can't be resolved
|
|
69
|
+
*/
|
|
70
|
+
function getReferencePath({ element, referenceHolder, referenceProperty, referencedRoot, node }) {
|
|
71
|
+
return pathConcat(
|
|
72
|
+
getPath(referenceHolder, element),
|
|
73
|
+
referenceProperty,
|
|
74
|
+
getPath(node, referencedRoot)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports.getReferencePath = getReferencePath;
|
|
79
|
+
|
|
27
80
|
function getEventDefinition(node) {
|
|
28
81
|
const eventDefinitions = node.get('eventDefinitions');
|
|
29
82
|
|
|
@@ -121,6 +174,10 @@ module.exports.hasDuplicatedPropertyValues = function(node, propertiesName, prop
|
|
|
121
174
|
return {
|
|
122
175
|
message: `Properties of type <${ duplicateProperties[ 0 ].$type }> have property <${ propertyName }> with duplicate value of <${ duplicate }>`,
|
|
123
176
|
path: null,
|
|
177
|
+
|
|
178
|
+
// one leaf path per offending field, so consumers resolve entry ids
|
|
179
|
+
// render-agnostically (template field vs. standard field)
|
|
180
|
+
...leafPaths(duplicateProperties, propertyName, parentNode),
|
|
124
181
|
data: {
|
|
125
182
|
type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED,
|
|
126
183
|
node,
|
|
@@ -169,6 +226,11 @@ module.exports.hasDuplicatedPropertiesValues = function(node, containerPropertyN
|
|
|
169
226
|
return {
|
|
170
227
|
message: `Properties of type <${ duplicate.$type }> have properties with duplicate values (${ duplicatesSummary })`,
|
|
171
228
|
path: null,
|
|
229
|
+
|
|
230
|
+
// one leaf path per offending location; `type` is the field that
|
|
231
|
+
// represents the duplicated element in the properties panel, so
|
|
232
|
+
// consumers resolve its entry id render-agnostically
|
|
233
|
+
...leafPaths(duplicateProperties, 'type', parentNode),
|
|
172
234
|
data: {
|
|
173
235
|
type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED,
|
|
174
236
|
node,
|
|
@@ -184,13 +246,13 @@ module.exports.hasDuplicatedPropertiesValues = function(node, containerPropertyN
|
|
|
184
246
|
return [];
|
|
185
247
|
};
|
|
186
248
|
|
|
187
|
-
module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
249
|
+
module.exports.hasProperties = function(node, properties, parentNode = null, nodePath) {
|
|
188
250
|
return Object.entries(properties).reduce((results, property) => {
|
|
189
251
|
const [ propertyName, propertyChecks ] = property;
|
|
190
252
|
|
|
191
253
|
const { allowedVersion = null } = propertyChecks;
|
|
192
254
|
|
|
193
|
-
const path = getPath(node, parentNode);
|
|
255
|
+
const path = nodePath === undefined ? getPath(node, parentNode) : nodePath;
|
|
194
256
|
|
|
195
257
|
const propertyValue = node.get(propertyName);
|
|
196
258
|
|
|
@@ -201,9 +263,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
201
263
|
message: allowedVersion
|
|
202
264
|
? `Element of type <${ node.$type }> without property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
|
|
203
265
|
: `Element of type <${ node.$type }> must have property <${ propertyName }>`,
|
|
204
|
-
path: path
|
|
205
|
-
? [ ...path, propertyName ]
|
|
206
|
-
: [ propertyName ],
|
|
266
|
+
path: pathConcat(path || [], propertyName),
|
|
207
267
|
data: addAllowedVersion({
|
|
208
268
|
type: ERROR_TYPES.PROPERTY_REQUIRED,
|
|
209
269
|
node,
|
|
@@ -222,9 +282,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
222
282
|
...results,
|
|
223
283
|
{
|
|
224
284
|
message: `Element of type <${ node.$type }> must have property <${ propertyName }> if it has property <${ propertyChecks.dependentRequired }>`,
|
|
225
|
-
path: path
|
|
226
|
-
? [ ...path, propertyName ]
|
|
227
|
-
: [ propertyName ],
|
|
285
|
+
path: pathConcat(path || [], propertyName),
|
|
228
286
|
data: {
|
|
229
287
|
type: ERROR_TYPES.PROPERTY_DEPENDENT_REQUIRED,
|
|
230
288
|
node,
|
|
@@ -252,9 +310,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
252
310
|
message: allowedVersion
|
|
253
311
|
? `Property <${ propertyName }> of type <${ propertyValue.$type }> only allowed by Camunda ${ allowedVersion } or newer`
|
|
254
312
|
: `Property <${ propertyName }> of type <${ propertyValue.$type }> not allowed`,
|
|
255
|
-
path: path
|
|
256
|
-
? [ ...path, propertyName ]
|
|
257
|
-
: [ propertyName ],
|
|
313
|
+
path: pathConcat(path || [], propertyName),
|
|
258
314
|
data: addAllowedVersion({
|
|
259
315
|
type: ERROR_TYPES.PROPERTY_TYPE_NOT_ALLOWED,
|
|
260
316
|
node,
|
|
@@ -271,9 +327,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
271
327
|
...results,
|
|
272
328
|
{
|
|
273
329
|
message: `Property <${ propertyName }> must have value of <${ propertyChecks.value }>`,
|
|
274
|
-
path: path
|
|
275
|
-
? [ ...path, propertyName ]
|
|
276
|
-
: [ propertyName ],
|
|
330
|
+
path: pathConcat(path || [], propertyName),
|
|
277
331
|
data: {
|
|
278
332
|
type: ERROR_TYPES.PROPERTY_VALUE_REQUIRED,
|
|
279
333
|
node,
|
|
@@ -292,9 +346,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
292
346
|
message: allowedVersion
|
|
293
347
|
? `Property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
|
|
294
348
|
: `Property <${ propertyName }> not allowed`,
|
|
295
|
-
path: path
|
|
296
|
-
? [ ...path, propertyName ]
|
|
297
|
-
: [ propertyName ],
|
|
349
|
+
path: pathConcat(path || [], propertyName),
|
|
298
350
|
data: addAllowedVersion({
|
|
299
351
|
type: ERROR_TYPES.PROPERTY_NOT_ALLOWED,
|
|
300
352
|
node,
|
|
@@ -312,9 +364,7 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
|
|
|
312
364
|
message: allowedVersion
|
|
313
365
|
? `Property value of <${ truncate(propertyValue) }> only allowed by Camunda ${ allowedVersion } or newer`
|
|
314
366
|
: `Property value of <${ truncate(propertyValue) }> not allowed`,
|
|
315
|
-
path: path
|
|
316
|
-
? [ ...path, propertyName ]
|
|
317
|
-
: [ propertyName ],
|
|
367
|
+
path: pathConcat(path || [], propertyName),
|
|
318
368
|
data: addAllowedVersion({
|
|
319
369
|
type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
|
|
320
370
|
node,
|
|
@@ -432,9 +482,7 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
|
|
|
432
482
|
return [
|
|
433
483
|
{
|
|
434
484
|
message: `Property <${ propertyName }> must have expression value`,
|
|
435
|
-
path: path
|
|
436
|
-
? [ ...path, propertyName ]
|
|
437
|
-
: null,
|
|
485
|
+
path: pathConcat(path, propertyName),
|
|
438
486
|
data: {
|
|
439
487
|
type: ERROR_TYPES.EXPRESSION_REQUIRED,
|
|
440
488
|
node: is(expression, 'bpmn:Expression') ? expression : node,
|
|
@@ -462,9 +510,7 @@ module.exports.hasExpression = function(node, propertyName, check, parentNode =
|
|
|
462
510
|
message: allowedVersion
|
|
463
511
|
? `Expression value of <${ propertyValue }> only allowed by Camunda ${ allowedVersion }`
|
|
464
512
|
: `Expression value of <${ propertyValue }> not allowed`,
|
|
465
|
-
path: path
|
|
466
|
-
? [ ...path, propertyName ]
|
|
467
|
-
: null,
|
|
513
|
+
path: pathConcat(path, propertyName),
|
|
468
514
|
data: addAllowedVersion({
|
|
469
515
|
type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
|
|
470
516
|
node: is(expression, 'bpmn:Expression') ? expression : node,
|