bpmnlint-plugin-camunda-compat 2.21.1 → 2.23.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 (56) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +39 -39
  3. package/index.js +237 -224
  4. package/package.json +53 -53
  5. package/rules/camunda-cloud/called-element.js +42 -42
  6. package/rules/camunda-cloud/collapsed-subprocess.js +40 -40
  7. package/rules/camunda-cloud/connector-properties/config.js +90 -90
  8. package/rules/camunda-cloud/connector-properties/index.js +47 -47
  9. package/rules/camunda-cloud/duplicate-execution-listeners.js +33 -0
  10. package/rules/camunda-cloud/duplicate-task-headers.js +58 -58
  11. package/rules/camunda-cloud/element-type/config.js +66 -66
  12. package/rules/camunda-cloud/element-type/index.js +133 -133
  13. package/rules/camunda-cloud/error-reference.js +71 -71
  14. package/rules/camunda-cloud/escalation-boundary-event-attached-to-ref.js +48 -48
  15. package/rules/camunda-cloud/escalation-reference.js +66 -66
  16. package/rules/camunda-cloud/event-based-gateway-target.js +38 -38
  17. package/rules/camunda-cloud/executable-process.js +61 -61
  18. package/rules/camunda-cloud/execution-listener.js +34 -0
  19. package/rules/camunda-cloud/feel.js +82 -82
  20. package/rules/camunda-cloud/implementation/config.js +16 -16
  21. package/rules/camunda-cloud/implementation/index.js +218 -218
  22. package/rules/camunda-cloud/inclusive-gateway.js +35 -35
  23. package/rules/camunda-cloud/link-event.js +142 -142
  24. package/rules/camunda-cloud/loop-characteristics.js +66 -66
  25. package/rules/camunda-cloud/message-reference.js +60 -60
  26. package/rules/camunda-cloud/no-binding-type.js +44 -0
  27. package/rules/camunda-cloud/no-candidate-users.js +38 -38
  28. package/rules/camunda-cloud/no-execution-listeners.js +21 -0
  29. package/rules/camunda-cloud/no-expression.js +173 -173
  30. package/rules/camunda-cloud/no-loop.js +316 -316
  31. package/rules/camunda-cloud/no-multiple-none-start-events.js +41 -41
  32. package/rules/camunda-cloud/no-propagate-all-parent-variables.js +44 -44
  33. package/rules/camunda-cloud/no-signal-event-sub-process.js +45 -45
  34. package/rules/camunda-cloud/no-task-schedule.js +18 -18
  35. package/rules/camunda-cloud/no-template.js +23 -23
  36. package/rules/camunda-cloud/no-zeebe-properties.js +18 -18
  37. package/rules/camunda-cloud/no-zeebe-user-task.js +27 -27
  38. package/rules/camunda-cloud/secrets.js +119 -119
  39. package/rules/camunda-cloud/sequence-flow-condition.js +56 -56
  40. package/rules/camunda-cloud/signal-reference.js +64 -64
  41. package/rules/camunda-cloud/start-event-form.js +97 -97
  42. package/rules/camunda-cloud/subscription.js +65 -65
  43. package/rules/camunda-cloud/task-schedule.js +67 -67
  44. package/rules/camunda-cloud/timer/config.js +46 -46
  45. package/rules/camunda-cloud/timer/index.js +183 -183
  46. package/rules/camunda-cloud/user-task-definition.js +24 -24
  47. package/rules/camunda-cloud/user-task-form.js +142 -142
  48. package/rules/camunda-cloud/wait-for-completion.js +46 -46
  49. package/rules/camunda-platform/history-time-to-live.js +19 -19
  50. package/rules/utils/cron.js +95 -95
  51. package/rules/utils/element.js +533 -484
  52. package/rules/utils/error-types.js +25 -24
  53. package/rules/utils/iso8601.js +52 -52
  54. package/rules/utils/reporter.js +37 -37
  55. package/rules/utils/rule.js +46 -46
  56. package/rules/utils/version.js +4 -4
@@ -1,485 +1,534 @@
1
- const {
2
- isArray,
3
- isDefined,
4
- isFunction,
5
- isNil,
6
- isObject,
7
- isString,
8
- isUndefined,
9
- some
10
- } = require('min-dash');
11
-
12
- const {
13
- is,
14
- isAny
15
- } = require('bpmnlint-utils');
16
-
17
- const { getPath } = require('@bpmn-io/moddle-utils');
18
-
19
- const { ERROR_TYPES } = require('./error-types');
20
-
21
- module.exports.ERROR_TYPES = ERROR_TYPES;
22
-
23
- function getEventDefinition(node) {
24
- const eventDefinitions = node.get('eventDefinitions');
25
-
26
- if (eventDefinitions) {
27
- return eventDefinitions[ 0 ];
28
- }
29
- }
30
-
31
- module.exports.getEventDefinition = getEventDefinition;
32
-
33
- module.exports.getMessageEventDefinition = function(node) {
34
- if (is(node, 'bpmn:ReceiveTask')) {
35
- return node;
36
- }
37
-
38
- return getEventDefinition(node);
39
- };
40
-
41
- function findExtensionElements(node, types) {
42
- const extensionElements = node.get('extensionElements');
43
-
44
- if (!extensionElements) {
45
- return;
46
- }
47
-
48
- const values = extensionElements.get('values');
49
-
50
- if (!values || !values.length) {
51
- return;
52
- }
53
-
54
- if (!isArray(types)) {
55
- types = [ types ];
56
- }
57
-
58
- return values.filter(value => isAny(value, types));
59
- }
60
-
61
- module.exports.findExtensionElements = findExtensionElements;
62
-
63
- function findExtensionElement(node, types) {
64
- const extensionElements = findExtensionElements(node, types);
65
-
66
- if (extensionElements && extensionElements.length) {
67
- return extensionElements[ 0 ];
68
- }
69
- }
70
-
71
- module.exports.findExtensionElement = findExtensionElement;
72
-
73
- function formatNames(names, exclusive = false) {
74
- return names.reduce((string, name, index) => {
75
-
76
- // first
77
- if (index === 0) {
78
- return `<${ name }>`;
79
- }
80
-
81
- // last
82
- if (index === names.length - 1) {
83
- return `${ string } ${ exclusive ? 'or' : 'and' } <${ name }>`;
84
- }
85
-
86
- return `${ string }, <${ name }>`;
87
- }, '');
88
- }
89
-
90
- module.exports.formatNames = formatNames;
91
-
92
- module.exports.hasDuplicatedPropertyValues = function(node, propertiesName, propertyName, parentNode = null) {
93
- const properties = node.get(propertiesName);
94
-
95
- const propertyValues = properties.map(property => property.get(propertyName));
96
-
97
- // (1) find duplicates
98
- const duplicates = propertyValues.reduce((duplicates, propertyValue, index) => {
99
- if (propertyValues.indexOf(propertyValue) !== index && !duplicates.includes(propertyValue)) {
100
- return [
101
- ...duplicates,
102
- propertyValue
103
- ];
104
- }
105
-
106
- return duplicates;
107
- }, []);
108
-
109
- // (2) report error for each duplicate
110
- if (duplicates.length) {
111
- return duplicates.map(duplicate => {
112
-
113
- // (3) find properties with duplicate
114
- const duplicateProperties = properties.filter(property => property.get(propertyName) === duplicate);
115
-
116
- // (4) report error
117
- return {
118
- message: `Properties of type <${ duplicateProperties[ 0 ].$type }> have property <${ propertyName }> with duplicate value of <${ duplicate }>`,
119
- path: null,
120
- data: {
121
- type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED,
122
- node,
123
- parentNode: parentNode == node ? null : parentNode,
124
- duplicatedProperty: propertyName,
125
- duplicatedPropertyValue: duplicate,
126
- properties: duplicateProperties,
127
- propertiesName
128
- }
129
- };
130
- });
131
- }
132
-
133
- return [];
134
- };
135
-
136
- module.exports.hasProperties = function(node, properties, parentNode = null) {
137
- return Object.entries(properties).reduce((results, property) => {
138
- const [ propertyName, propertyChecks ] = property;
139
-
140
- const { allowedVersion = null } = propertyChecks;
141
-
142
- const path = getPath(node, parentNode);
143
-
144
- const propertyValue = node.get(propertyName);
145
-
146
- if (propertyChecks.required && isEmptyValue(propertyValue)) {
147
- return [
148
- ...results,
149
- {
150
- message: allowedVersion
151
- ? `Element of type <${ node.$type }> without property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
152
- : `Element of type <${ node.$type }> must have property <${ propertyName }>`,
153
- path: path
154
- ? [ ...path, propertyName ]
155
- : [ propertyName ],
156
- data: addAllowedVersion({
157
- type: ERROR_TYPES.PROPERTY_REQUIRED,
158
- node,
159
- parentNode: parentNode == node ? null : parentNode,
160
- requiredProperty: propertyName
161
- }, allowedVersion)
162
- }
163
- ];
164
- }
165
-
166
- if (propertyChecks.dependentRequired) {
167
- const dependency = node.get(propertyChecks.dependentRequired);
168
-
169
- if (dependency && isEmptyValue(propertyValue)) {
170
- return [
171
- ...results,
172
- {
173
- message: `Element of type <${ node.$type }> must have property <${ propertyName }> if it has property <${ propertyChecks.dependentRequired }>`,
174
- path: path
175
- ? [ ...path, propertyName ]
176
- : [ propertyName ],
177
- data: {
178
- type: ERROR_TYPES.PROPERTY_DEPENDENT_REQUIRED,
179
- node,
180
- parentNode: parentNode == node ? null : parentNode,
181
- property: propertyChecks.dependentRequired,
182
- dependentRequiredProperty: propertyName
183
- }
184
- }
185
- ];
186
- }
187
- }
188
-
189
- if (
190
- propertyChecks.type
191
- && propertyValue
192
- && (
193
- !propertyValue.$instanceOf
194
- || (!isArray(propertyChecks.type) && !propertyValue.$instanceOf(propertyChecks.type))
195
- || (isArray(propertyChecks.type) && !some(propertyChecks.type, type => propertyValue.$instanceOf(type)))
196
- )
197
- ) {
198
- return [
199
- ...results,
200
- {
201
- message: allowedVersion
202
- ? `Property <${ propertyName }> of type <${ propertyValue.$type }> only allowed by Camunda ${ allowedVersion } or newer`
203
- : `Property <${ propertyName }> of type <${ propertyValue.$type }> not allowed`,
204
- path: path
205
- ? [ ...path, propertyName ]
206
- : [ propertyName ],
207
- data: addAllowedVersion({
208
- type: ERROR_TYPES.PROPERTY_TYPE_NOT_ALLOWED,
209
- node,
210
- parentNode: parentNode == node ? null : parentNode,
211
- property: propertyName,
212
- allowedPropertyType: propertyChecks.type
213
- }, allowedVersion)
214
- }
215
- ];
216
- }
217
-
218
- if ('value' in propertyChecks && propertyChecks.value !== propertyValue) {
219
- return [
220
- ...results,
221
- {
222
- message: `Property <${ propertyName }> must have value of <${ propertyChecks.value }>`,
223
- path: path
224
- ? [ ...path, propertyName ]
225
- : [ propertyName ],
226
- data: {
227
- type: ERROR_TYPES.PROPERTY_VALUE_REQUIRED,
228
- node,
229
- parentNode: parentNode == node ? null : parentNode,
230
- property: propertyName,
231
- requiredValue: propertyChecks.value
232
- }
233
- }
234
- ];
235
- }
236
-
237
- if (propertyChecks.allowed === false && isDefined(propertyValue) && !isNil(propertyValue)) {
238
- return [
239
- ...results,
240
- {
241
- message: allowedVersion
242
- ? `Property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
243
- : `Property <${ propertyName }> not allowed`,
244
- path: path
245
- ? [ ...path, propertyName ]
246
- : [ propertyName ],
247
- data: addAllowedVersion({
248
- type: ERROR_TYPES.PROPERTY_NOT_ALLOWED,
249
- node,
250
- parentNode: parentNode == node ? null : parentNode,
251
- property: propertyName
252
- }, allowedVersion)
253
- }
254
- ];
255
- }
256
-
257
- if (isFunction(propertyChecks.allowed) && !propertyChecks.allowed(propertyValue)) {
258
- return [
259
- ...results,
260
- {
261
- message: allowedVersion
262
- ? `Property value of <${ truncate(propertyValue) }> only allowed by Camunda ${ allowedVersion } or newer`
263
- : `Property value of <${ truncate(propertyValue) }> not allowed`,
264
- path: path
265
- ? [ ...path, propertyName ]
266
- : [ propertyName ],
267
- data: addAllowedVersion({
268
- type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
269
- node,
270
- parentNode: parentNode == node ? null : parentNode,
271
- property: propertyName
272
- }, allowedVersion)
273
- }
274
- ];
275
- }
276
-
277
- return results;
278
- }, []);
279
- };
280
-
281
- module.exports.hasProperty = function(node, propertyNames, parentNode = null) {
282
- propertyNames = isArray(propertyNames) ? propertyNames : [ propertyNames ];
283
-
284
- const properties = findProperties(node, propertyNames);
285
-
286
- if (properties.length !== 1) {
287
- return [
288
- {
289
- message: `Element of type <${ node.$type }> must have property ${ formatNames(propertyNames, true) }`,
290
- path: getPath(node, parentNode),
291
- data: {
292
- type: ERROR_TYPES.PROPERTY_REQUIRED,
293
- node,
294
- parentNode: parentNode == node ? null : parentNode,
295
- requiredProperty: propertyNames
296
- }
297
- }
298
- ];
299
- }
300
-
301
- return [];
302
- };
303
-
304
- function findProperties(node, propertyNames) {
305
- const properties = [];
306
-
307
- for (const propertyName of propertyNames) {
308
- const propertyValue = node.get(propertyName);
309
-
310
- if (!isEmptyValue(propertyValue)) {
311
- properties.push(node.get(propertyName));
312
- }
313
- }
314
-
315
- return properties;
316
- }
317
-
318
- module.exports.hasExtensionElement = function(node, types, parentNode = null) {
319
- const typesArray = isArray(types) ? types : [ types ];
320
-
321
- const extensionElements = findExtensionElements(node, typesArray);
322
-
323
- if (!extensionElements || extensionElements.length !== 1) {
324
- return [
325
- {
326
- message: `Element of type <${ node.$type }> must have one extension element of type ${ formatNames(typesArray, true) }`,
327
- path: getPath(node, parentNode),
328
- data: {
329
- type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
330
- node,
331
- parentNode: parentNode == node ? null : parentNode,
332
- requiredExtensionElement: types
333
- }
334
- }
335
- ];
336
- }
337
-
338
- return [];
339
- };
340
-
341
- module.exports.hasNoExtensionElement = function(node, type, parentNode = null, allowedVersion = null) {
342
- const extensionElement = findExtensionElement(node, type);
343
-
344
- if (extensionElement) {
345
- return [
346
- {
347
- message: allowedVersion
348
- ? `Extension element of type <${ type }> only allowed by Camunda ${ allowedVersion }`
349
- : `Extension element of type <${ type }> not allowed`,
350
- path: getPath(extensionElement, parentNode),
351
- data: addAllowedVersion({
352
- type: ERROR_TYPES.EXTENSION_ELEMENT_NOT_ALLOWED,
353
- node,
354
- parentNode: parentNode == node ? null : parentNode,
355
- extensionElement
356
- }, allowedVersion)
357
- }
358
- ];
359
- }
360
-
361
- return [];
362
- };
363
-
364
- module.exports.hasExpression = function(node, propertyName, check, parentNode = null) {
365
- const expression = node.get(propertyName);
366
-
367
- if (!expression) {
368
- throw new Error('Expression not found');
369
- }
370
-
371
- let propertyValue = expression;
372
-
373
- if (is(expression, 'bpmn:Expression')) {
374
- propertyValue = expression.get('body');
375
- }
376
-
377
- const path = getPath(node, parentNode);
378
-
379
- if (!propertyValue) {
380
- if (check.required !== false) {
381
- return [
382
- {
383
- message: `Property <${ propertyName }> must have expression value`,
384
- path: path
385
- ? [ ...path, propertyName ]
386
- : null,
387
- data: {
388
- type: ERROR_TYPES.EXPRESSION_REQUIRED,
389
- node: is(expression, 'bpmn:Expression') ? expression : node,
390
- parentNode,
391
- property: propertyName
392
- }
393
- }
394
- ];
395
- }
396
-
397
- return [];
398
- }
399
-
400
- const allowed = check.allowed(propertyValue);
401
-
402
- if (allowed !== true) {
403
- let allowedVersion = null;
404
-
405
- if (isObject(allowed)) {
406
- ({ allowedVersion } = allowed);
407
- }
408
-
409
- return [
410
- {
411
- message: allowedVersion
412
- ? `Expression value of <${ propertyValue }> only allowed by Camunda ${ allowedVersion }`
413
- : `Expression value of <${ propertyValue }> not allowed`,
414
- path: path
415
- ? [ ...path, propertyName ]
416
- : null,
417
- data: addAllowedVersion({
418
- type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
419
- node: is(expression, 'bpmn:Expression') ? expression : node,
420
- parentNode,
421
- property: propertyName
422
- }, allowedVersion)
423
- }
424
- ];
425
- }
426
-
427
- return [];
428
- };
429
-
430
- function isExactly(node, type) {
431
- const { $model } = node;
432
-
433
- return $model.getType(node.$type) === $model.getType(type);
434
- }
435
-
436
- module.exports.isExactly = isExactly;
437
-
438
- module.exports.isAnyExactly = function(node, types) {
439
- return some(types, (type) => isExactly(node, type));
440
- };
441
-
442
- function truncate(string, maxLength = 10) {
443
- const stringified = `${ string }`;
444
-
445
- return stringified.length > maxLength ? `${ stringified.slice(0, maxLength) }...` : stringified;
446
- }
447
-
448
- function addAllowedVersion(data, allowedVersion) {
449
- if (!allowedVersion) {
450
- return data;
451
- }
452
-
453
- return {
454
- ...data,
455
- allowedVersion
456
- };
457
- }
458
-
459
- function findParent(node, type) {
460
- if (!node) {
461
- return null;
462
- }
463
-
464
- const parent = node.$parent;
465
-
466
- if (!parent) {
467
- return node;
468
- }
469
-
470
- if (is(parent, type)) {
471
- return parent;
472
- }
473
-
474
- return findParent(parent, type);
475
- }
476
-
477
- module.exports.findParent = findParent;
478
-
479
- function isEmptyString(value) {
480
- return isString(value) && value.trim() === '';
481
- }
482
-
483
- function isEmptyValue(value) {
484
- return isUndefined(value) || isNil(value) || isEmptyString(value);
1
+ const {
2
+ filter,
3
+ isArray,
4
+ isDefined,
5
+ isFunction,
6
+ isNil,
7
+ isObject,
8
+ isString,
9
+ isUndefined,
10
+ matchPattern,
11
+ some
12
+ } = require('min-dash');
13
+
14
+ const {
15
+ is,
16
+ isAny
17
+ } = require('bpmnlint-utils');
18
+
19
+ const { getPath } = require('@bpmn-io/moddle-utils');
20
+
21
+ const { ERROR_TYPES } = require('./error-types');
22
+
23
+ module.exports.ERROR_TYPES = ERROR_TYPES;
24
+
25
+ function getEventDefinition(node) {
26
+ const eventDefinitions = node.get('eventDefinitions');
27
+
28
+ if (eventDefinitions) {
29
+ return eventDefinitions[ 0 ];
30
+ }
31
+ }
32
+
33
+ module.exports.getEventDefinition = getEventDefinition;
34
+
35
+ module.exports.getMessageEventDefinition = function(node) {
36
+ if (is(node, 'bpmn:ReceiveTask')) {
37
+ return node;
38
+ }
39
+
40
+ return getEventDefinition(node);
41
+ };
42
+
43
+ function findExtensionElements(node, types) {
44
+ const extensionElements = node.get('extensionElements');
45
+
46
+ if (!extensionElements) {
47
+ return;
48
+ }
49
+
50
+ const values = extensionElements.get('values');
51
+
52
+ if (!values || !values.length) {
53
+ return;
54
+ }
55
+
56
+ if (!isArray(types)) {
57
+ types = [ types ];
58
+ }
59
+
60
+ return values.filter(value => isAny(value, types));
61
+ }
62
+
63
+ module.exports.findExtensionElements = findExtensionElements;
64
+
65
+ function findExtensionElement(node, types) {
66
+ const extensionElements = findExtensionElements(node, types);
67
+
68
+ if (extensionElements && extensionElements.length) {
69
+ return extensionElements[ 0 ];
70
+ }
71
+ }
72
+
73
+ module.exports.findExtensionElement = findExtensionElement;
74
+
75
+ function formatNames(names, exclusive = false) {
76
+ return names.reduce((string, name, index) => {
77
+
78
+ // first
79
+ if (index === 0) {
80
+ return `<${ name }>`;
81
+ }
82
+
83
+ // last
84
+ if (index === names.length - 1) {
85
+ return `${ string } ${ exclusive ? 'or' : 'and' } <${ name }>`;
86
+ }
87
+
88
+ return `${ string }, <${ name }>`;
89
+ }, '');
90
+ }
91
+
92
+ module.exports.formatNames = formatNames;
93
+
94
+ module.exports.hasDuplicatedPropertyValues = function(node, propertiesName, propertyName, parentNode = null) {
95
+ const properties = node.get(propertiesName);
96
+
97
+ const propertyValues = properties.map(property => property.get(propertyName));
98
+
99
+ // (1) find duplicates
100
+ const duplicates = propertyValues.reduce((duplicates, propertyValue, index) => {
101
+ if (propertyValues.indexOf(propertyValue) !== index && !duplicates.includes(propertyValue)) {
102
+ return [
103
+ ...duplicates,
104
+ propertyValue
105
+ ];
106
+ }
107
+
108
+ return duplicates;
109
+ }, []);
110
+
111
+ // (2) report error for each duplicate
112
+ if (duplicates.length) {
113
+ return duplicates.map(duplicate => {
114
+
115
+ // (3) find properties with duplicate
116
+ const duplicateProperties = properties.filter(property => property.get(propertyName) === duplicate);
117
+
118
+ // (4) report error
119
+ return {
120
+ message: `Properties of type <${ duplicateProperties[ 0 ].$type }> have property <${ propertyName }> with duplicate value of <${ duplicate }>`,
121
+ path: null,
122
+ data: {
123
+ type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED,
124
+ node,
125
+ parentNode: parentNode == node ? null : parentNode,
126
+ duplicatedProperty: propertyName,
127
+ duplicatedPropertyValue: duplicate,
128
+ properties: duplicateProperties,
129
+ propertiesName
130
+ }
131
+ };
132
+ });
133
+ }
134
+
135
+ return [];
136
+ };
137
+
138
+ // @TODO(@barmac): use tree algorithm to reduce complexity
139
+ module.exports.hasDuplicatedPropertiesValues = function(node, containerPropertyName, propertiesNames, parentNode = null) {
140
+ const properties = node.get(containerPropertyName);
141
+
142
+ // (1) find duplicates
143
+ const duplicates = properties.reduce((foundDuplicates, property, index) => {
144
+ const previous = properties.slice(0, index);
145
+ const isDuplicate = previous.find(p => propertiesNames.every(propertyName => p.get(propertyName) === property.get(propertyName)));
146
+
147
+ if (isDuplicate) {
148
+ return foundDuplicates.concat(property);
149
+ }
150
+
151
+ return foundDuplicates;
152
+ }, []);
153
+
154
+ // (2) report error for each duplicate
155
+ if (duplicates.length) {
156
+ return duplicates.map(duplicate => {
157
+ const propertiesMap = {};
158
+ for (const property of propertiesNames) {
159
+ propertiesMap[property] = duplicate.get(property);
160
+ }
161
+
162
+ // (3) find properties with duplicate
163
+ const duplicateProperties = filter(properties, matchPattern(propertiesMap));
164
+ const duplicatesSummary = propertiesNames.map(propertyName => `property <${ propertyName }> with duplicate value of <${ propertiesMap[propertyName] }>`).join(', ');
165
+
166
+ // (4) report error
167
+ return {
168
+ message: `Properties of type <${ duplicate.$type }> have properties with duplicate values (${ duplicatesSummary })`,
169
+ path: null,
170
+ data: {
171
+ type: ERROR_TYPES.PROPERTY_VALUES_DUPLICATED,
172
+ node,
173
+ parentNode: parentNode == node ? null : parentNode,
174
+ duplicatedProperties: propertiesMap,
175
+ properties: duplicateProperties,
176
+ propertiesName: containerPropertyName
177
+ }
178
+ };
179
+ });
180
+ }
181
+
182
+ return [];
183
+ };
184
+
185
+ module.exports.hasProperties = function(node, properties, parentNode = null) {
186
+ return Object.entries(properties).reduce((results, property) => {
187
+ const [ propertyName, propertyChecks ] = property;
188
+
189
+ const { allowedVersion = null } = propertyChecks;
190
+
191
+ const path = getPath(node, parentNode);
192
+
193
+ const propertyValue = node.get(propertyName);
194
+
195
+ if (propertyChecks.required && isEmptyValue(propertyValue)) {
196
+ return [
197
+ ...results,
198
+ {
199
+ message: allowedVersion
200
+ ? `Element of type <${ node.$type }> without property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
201
+ : `Element of type <${ node.$type }> must have property <${ propertyName }>`,
202
+ path: path
203
+ ? [ ...path, propertyName ]
204
+ : [ propertyName ],
205
+ data: addAllowedVersion({
206
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
207
+ node,
208
+ parentNode: parentNode == node ? null : parentNode,
209
+ requiredProperty: propertyName
210
+ }, allowedVersion)
211
+ }
212
+ ];
213
+ }
214
+
215
+ if (propertyChecks.dependentRequired) {
216
+ const dependency = node.get(propertyChecks.dependentRequired);
217
+
218
+ if (dependency && isEmptyValue(propertyValue)) {
219
+ return [
220
+ ...results,
221
+ {
222
+ message: `Element of type <${ node.$type }> must have property <${ propertyName }> if it has property <${ propertyChecks.dependentRequired }>`,
223
+ path: path
224
+ ? [ ...path, propertyName ]
225
+ : [ propertyName ],
226
+ data: {
227
+ type: ERROR_TYPES.PROPERTY_DEPENDENT_REQUIRED,
228
+ node,
229
+ parentNode: parentNode == node ? null : parentNode,
230
+ property: propertyChecks.dependentRequired,
231
+ dependentRequiredProperty: propertyName
232
+ }
233
+ }
234
+ ];
235
+ }
236
+ }
237
+
238
+ if (
239
+ propertyChecks.type
240
+ && propertyValue
241
+ && (
242
+ !propertyValue.$instanceOf
243
+ || (!isArray(propertyChecks.type) && !propertyValue.$instanceOf(propertyChecks.type))
244
+ || (isArray(propertyChecks.type) && !some(propertyChecks.type, type => propertyValue.$instanceOf(type)))
245
+ )
246
+ ) {
247
+ return [
248
+ ...results,
249
+ {
250
+ message: allowedVersion
251
+ ? `Property <${ propertyName }> of type <${ propertyValue.$type }> only allowed by Camunda ${ allowedVersion } or newer`
252
+ : `Property <${ propertyName }> of type <${ propertyValue.$type }> not allowed`,
253
+ path: path
254
+ ? [ ...path, propertyName ]
255
+ : [ propertyName ],
256
+ data: addAllowedVersion({
257
+ type: ERROR_TYPES.PROPERTY_TYPE_NOT_ALLOWED,
258
+ node,
259
+ parentNode: parentNode == node ? null : parentNode,
260
+ property: propertyName,
261
+ allowedPropertyType: propertyChecks.type
262
+ }, allowedVersion)
263
+ }
264
+ ];
265
+ }
266
+
267
+ if ('value' in propertyChecks && propertyChecks.value !== propertyValue) {
268
+ return [
269
+ ...results,
270
+ {
271
+ message: `Property <${ propertyName }> must have value of <${ propertyChecks.value }>`,
272
+ path: path
273
+ ? [ ...path, propertyName ]
274
+ : [ propertyName ],
275
+ data: {
276
+ type: ERROR_TYPES.PROPERTY_VALUE_REQUIRED,
277
+ node,
278
+ parentNode: parentNode == node ? null : parentNode,
279
+ property: propertyName,
280
+ requiredValue: propertyChecks.value
281
+ }
282
+ }
283
+ ];
284
+ }
285
+
286
+ if (propertyChecks.allowed === false && isDefined(propertyValue) && !isNil(propertyValue)) {
287
+ return [
288
+ ...results,
289
+ {
290
+ message: allowedVersion
291
+ ? `Property <${ propertyName }> only allowed by Camunda ${ allowedVersion } or newer`
292
+ : `Property <${ propertyName }> not allowed`,
293
+ path: path
294
+ ? [ ...path, propertyName ]
295
+ : [ propertyName ],
296
+ data: addAllowedVersion({
297
+ type: ERROR_TYPES.PROPERTY_NOT_ALLOWED,
298
+ node,
299
+ parentNode: parentNode == node ? null : parentNode,
300
+ property: propertyName
301
+ }, allowedVersion)
302
+ }
303
+ ];
304
+ }
305
+
306
+ if (isFunction(propertyChecks.allowed) && !propertyChecks.allowed(propertyValue)) {
307
+ return [
308
+ ...results,
309
+ {
310
+ message: allowedVersion
311
+ ? `Property value of <${ truncate(propertyValue) }> only allowed by Camunda ${ allowedVersion } or newer`
312
+ : `Property value of <${ truncate(propertyValue) }> not allowed`,
313
+ path: path
314
+ ? [ ...path, propertyName ]
315
+ : [ propertyName ],
316
+ data: addAllowedVersion({
317
+ type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
318
+ node,
319
+ parentNode: parentNode == node ? null : parentNode,
320
+ property: propertyName
321
+ }, allowedVersion)
322
+ }
323
+ ];
324
+ }
325
+
326
+ return results;
327
+ }, []);
328
+ };
329
+
330
+ module.exports.hasProperty = function(node, propertyNames, parentNode = null) {
331
+ propertyNames = isArray(propertyNames) ? propertyNames : [ propertyNames ];
332
+
333
+ const properties = findProperties(node, propertyNames);
334
+
335
+ if (properties.length !== 1) {
336
+ return [
337
+ {
338
+ message: `Element of type <${ node.$type }> must have property ${ formatNames(propertyNames, true) }`,
339
+ path: getPath(node, parentNode),
340
+ data: {
341
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
342
+ node,
343
+ parentNode: parentNode == node ? null : parentNode,
344
+ requiredProperty: propertyNames
345
+ }
346
+ }
347
+ ];
348
+ }
349
+
350
+ return [];
351
+ };
352
+
353
+ function findProperties(node, propertyNames) {
354
+ const properties = [];
355
+
356
+ for (const propertyName of propertyNames) {
357
+ const propertyValue = node.get(propertyName);
358
+
359
+ if (!isEmptyValue(propertyValue)) {
360
+ properties.push(node.get(propertyName));
361
+ }
362
+ }
363
+
364
+ return properties;
365
+ }
366
+
367
+ module.exports.hasExtensionElement = function(node, types, parentNode = null) {
368
+ const typesArray = isArray(types) ? types : [ types ];
369
+
370
+ const extensionElements = findExtensionElements(node, typesArray);
371
+
372
+ if (!extensionElements || extensionElements.length !== 1) {
373
+ return [
374
+ {
375
+ message: `Element of type <${ node.$type }> must have one extension element of type ${ formatNames(typesArray, true) }`,
376
+ path: getPath(node, parentNode),
377
+ data: {
378
+ type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
379
+ node,
380
+ parentNode: parentNode == node ? null : parentNode,
381
+ requiredExtensionElement: types
382
+ }
383
+ }
384
+ ];
385
+ }
386
+
387
+ return [];
388
+ };
389
+
390
+ module.exports.hasNoExtensionElement = function(node, type, parentNode = null, allowedVersion = null) {
391
+ const extensionElement = findExtensionElement(node, type);
392
+
393
+ if (extensionElement) {
394
+ return [
395
+ {
396
+ message: allowedVersion
397
+ ? `Extension element of type <${ type }> only allowed by Camunda ${ allowedVersion }`
398
+ : `Extension element of type <${ type }> not allowed`,
399
+ path: getPath(extensionElement, parentNode),
400
+ data: addAllowedVersion({
401
+ type: ERROR_TYPES.EXTENSION_ELEMENT_NOT_ALLOWED,
402
+ node,
403
+ parentNode: parentNode == node ? null : parentNode,
404
+ extensionElement
405
+ }, allowedVersion)
406
+ }
407
+ ];
408
+ }
409
+
410
+ return [];
411
+ };
412
+
413
+ module.exports.hasExpression = function(node, propertyName, check, parentNode = null) {
414
+ const expression = node.get(propertyName);
415
+
416
+ if (!expression) {
417
+ throw new Error('Expression not found');
418
+ }
419
+
420
+ let propertyValue = expression;
421
+
422
+ if (is(expression, 'bpmn:Expression')) {
423
+ propertyValue = expression.get('body');
424
+ }
425
+
426
+ const path = getPath(node, parentNode);
427
+
428
+ if (!propertyValue) {
429
+ if (check.required !== false) {
430
+ return [
431
+ {
432
+ message: `Property <${ propertyName }> must have expression value`,
433
+ path: path
434
+ ? [ ...path, propertyName ]
435
+ : null,
436
+ data: {
437
+ type: ERROR_TYPES.EXPRESSION_REQUIRED,
438
+ node: is(expression, 'bpmn:Expression') ? expression : node,
439
+ parentNode,
440
+ property: propertyName
441
+ }
442
+ }
443
+ ];
444
+ }
445
+
446
+ return [];
447
+ }
448
+
449
+ const allowed = check.allowed(propertyValue);
450
+
451
+ if (allowed !== true) {
452
+ let allowedVersion = null;
453
+
454
+ if (isObject(allowed)) {
455
+ ({ allowedVersion } = allowed);
456
+ }
457
+
458
+ return [
459
+ {
460
+ message: allowedVersion
461
+ ? `Expression value of <${ propertyValue }> only allowed by Camunda ${ allowedVersion }`
462
+ : `Expression value of <${ propertyValue }> not allowed`,
463
+ path: path
464
+ ? [ ...path, propertyName ]
465
+ : null,
466
+ data: addAllowedVersion({
467
+ type: ERROR_TYPES.EXPRESSION_VALUE_NOT_ALLOWED,
468
+ node: is(expression, 'bpmn:Expression') ? expression : node,
469
+ parentNode,
470
+ property: propertyName
471
+ }, allowedVersion)
472
+ }
473
+ ];
474
+ }
475
+
476
+ return [];
477
+ };
478
+
479
+ function isExactly(node, type) {
480
+ const { $model } = node;
481
+
482
+ return $model.getType(node.$type) === $model.getType(type);
483
+ }
484
+
485
+ module.exports.isExactly = isExactly;
486
+
487
+ module.exports.isAnyExactly = function(node, types) {
488
+ return some(types, (type) => isExactly(node, type));
489
+ };
490
+
491
+ function truncate(string, maxLength = 10) {
492
+ const stringified = `${ string }`;
493
+
494
+ return stringified.length > maxLength ? `${ stringified.slice(0, maxLength) }...` : stringified;
495
+ }
496
+
497
+ function addAllowedVersion(data, allowedVersion) {
498
+ if (!allowedVersion) {
499
+ return data;
500
+ }
501
+
502
+ return {
503
+ ...data,
504
+ allowedVersion
505
+ };
506
+ }
507
+
508
+ function findParent(node, type) {
509
+ if (!node) {
510
+ return null;
511
+ }
512
+
513
+ const parent = node.$parent;
514
+
515
+ if (!parent) {
516
+ return node;
517
+ }
518
+
519
+ if (is(parent, type)) {
520
+ return parent;
521
+ }
522
+
523
+ return findParent(parent, type);
524
+ }
525
+
526
+ module.exports.findParent = findParent;
527
+
528
+ function isEmptyString(value) {
529
+ return isString(value) && value.trim() === '';
530
+ }
531
+
532
+ function isEmptyValue(value) {
533
+ return isUndefined(value) || isNil(value) || isEmptyString(value);
485
534
  }