bpmnlint-plugin-camunda-compat 2.22.0 → 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 -231
  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 -33
  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 -34
  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 -21
  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 -533
  52. package/rules/utils/error-types.js +25 -25
  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,534 +1,534 @@
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);
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);
534
534
  }