bpmnlint-plugin-camunda-compat 0.3.0 → 0.6.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.
@@ -0,0 +1,667 @@
1
+ const {
2
+ isArray,
3
+ isObject,
4
+ isString,
5
+ some
6
+ } = require('min-dash');
7
+
8
+ const {
9
+ is,
10
+ isAny
11
+ } = require('bpmnlint-utils');
12
+
13
+ const { getPath } = require('@philippfromme/moddle-helpers');
14
+
15
+ const ERROR_TYPES = Object.freeze({
16
+ ELEMENT_TYPE: 'elementType',
17
+ EXTENSION_ELEMENT_REQUIRED: 'extensionElementRequired',
18
+ PROPERTY_DEPENDEND_REQUIRED: 'propertyDependendRequired',
19
+ PROPERTY_REQUIRED: 'propertyRequired',
20
+ PROPERTY_TYPE: 'propertyType'
21
+ });
22
+
23
+ module.exports.ERROR_TYPES = ERROR_TYPES;
24
+
25
+ /**
26
+ * @param {ModdleElement} node
27
+ *
28
+ * @returns {boolean|string}
29
+ */
30
+ function hasNoEventDefinition(node) {
31
+ const eventDefinitions = node.get('eventDefinitions');
32
+
33
+ return !eventDefinitions
34
+ || !eventDefinitions.length
35
+ || getElementNotSupportedError(node.$type, eventDefinitions[ 0 ].$type, [ 'eventDefinitions', 0 ]);
36
+ }
37
+
38
+ module.exports.hasNoEventDefinition = hasNoEventDefinition;
39
+
40
+ /**
41
+ * @param {ModdleElement} node
42
+ * @param {string[]} types
43
+ *
44
+ * @returns {boolean|string}
45
+ */
46
+ function hasEventDefinitionOfType(node, types) {
47
+ if (!isArray(types)) {
48
+ types = [ types ];
49
+ }
50
+
51
+ const eventDefinitions = node.get('eventDefinitions');
52
+
53
+ if (!eventDefinitions || eventDefinitions.length !== 1) {
54
+ return getElementNotSupportedError(node.$type, null);
55
+ }
56
+
57
+ const eventDefinition = eventDefinitions[ 0 ];
58
+
59
+ return isAny(eventDefinition, types)
60
+ || getElementNotSupportedError(node.$type, eventDefinition.$type, [ 'eventDefinitions', 0 ]);
61
+ }
62
+
63
+ /**
64
+ * @param {string[]} types
65
+ *
66
+ * @returns {Function}
67
+ */
68
+ module.exports.hasEventDefinitionOfType = function(types) {
69
+ return function(node) {
70
+ return hasEventDefinitionOfType(node, types);
71
+ };
72
+ };
73
+
74
+ /**
75
+ * @param {string[]} types
76
+ *
77
+ * @returns {Function}
78
+ */
79
+ module.exports.hasEventDefinitionOfTypeOrNone = function(types) {
80
+ return function(node) {
81
+ const results = [
82
+ hasNoEventDefinition(node),
83
+ hasEventDefinitionOfType(node, types)
84
+ ];
85
+
86
+ return some(results, result => result === true)
87
+ || results.find(result => isArray(result) || isObject(result) || isString(result));
88
+ };
89
+ };
90
+
91
+ module.exports.hasLoopCharacteristics = function(node) {
92
+ return !!node.get('loopCharacteristics');
93
+ };
94
+
95
+ /**
96
+ * @param {string[]} types
97
+ *
98
+ * @returns {boolean|string}
99
+ */
100
+ module.exports.hasLoopCharacteristicsOfTypeOrNone = function(type) {
101
+ return function(node) {
102
+ const loopCharacteristics = node.get('loopCharacteristics');
103
+
104
+ if (!loopCharacteristics) {
105
+ return true;
106
+ }
107
+
108
+ return is(loopCharacteristics, type)
109
+ || getElementNotSupportedError(node.$type, loopCharacteristics.$type, [ 'loopCharacteristics' ]);
110
+ };
111
+ };
112
+
113
+ /**
114
+ * @param {ModdleElement} node
115
+ *
116
+ * @returns {boolean|Object|Object[]}
117
+ */
118
+ module.exports.hasMultiInstanceLoopCharacteristics = function(node) {
119
+ const results = checkProperties(node, {
120
+ loopCharacteristics: {
121
+ type: 'bpmn:MultiInstanceLoopCharacteristics'
122
+ }
123
+ }, node);
124
+
125
+ if (results.length === 1) {
126
+ return results[ 0 ];
127
+ } else if (results.length > 1) {
128
+ return results;
129
+ }
130
+
131
+ return true;
132
+ };
133
+
134
+ module.exports.hasNoLanes = function(node) {
135
+ const laneSets = node.get('laneSets');
136
+
137
+ return !laneSets
138
+ || !laneSets.length
139
+ || getElementNotSupportedError(node.$type, 'bpmn:LaneSet', [ 'laneSets' ]);
140
+ };
141
+
142
+ module.exports.isNotBpmn = function(node) {
143
+ return !is(node, 'bpmn:BaseElement');
144
+ };
145
+
146
+ function findExtensionElement(node, types) {
147
+ const extensionElements = findExtensionElements(node, types);
148
+
149
+ if (extensionElements && extensionElements.length) {
150
+ return extensionElements[ 0 ];
151
+ }
152
+ }
153
+
154
+ function findExtensionElements(node, types) {
155
+ const extensionElements = node.get('extensionElements');
156
+
157
+ if (!extensionElements) {
158
+ return;
159
+ }
160
+
161
+ const values = extensionElements.get('values');
162
+
163
+ if (!values || !values.length) {
164
+ return;
165
+ }
166
+
167
+ if (!isArray(types)) {
168
+ types = [ types ];
169
+ }
170
+
171
+ return values.filter(value => isAny(value, types));
172
+ }
173
+
174
+ module.exports.findExtensionElement = findExtensionElement;
175
+
176
+ function getElementNotSupportedError(type, propertyType, path = null) {
177
+ if (propertyType) {
178
+ return {
179
+ message: `Element of type <${ type }> (<${ propertyType }>) not supported by {{ executionPlatform }}`,
180
+ path,
181
+ error: {
182
+ type: ERROR_TYPES.ELEMENT_TYPE,
183
+ elementType: type,
184
+ propertyType
185
+ }
186
+ };
187
+ }
188
+
189
+ return {
190
+ message: `Element of type <${ type }> not supported by {{ executionPlatform }}`,
191
+ path,
192
+ error: {
193
+ type: ERROR_TYPES.ELEMENT_TYPE,
194
+ elementType: type
195
+ }
196
+ };
197
+ }
198
+
199
+ /**
200
+ * @param {string|Object} type
201
+ *
202
+ * @returns {string}
203
+ */
204
+ function getType(type) {
205
+ if (isObject(type)) {
206
+ return type.type;
207
+ }
208
+
209
+ return type;
210
+ }
211
+
212
+ /**
213
+ * @param {string|Object} type
214
+ *
215
+ * @returns {string|Object}
216
+ */
217
+ function getProperties(type) {
218
+ if (isObject(type)) {
219
+ return type.properties;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * @param {Function} check
225
+ * @param {Function} ifFn
226
+ * @param {*} [elseReturnValue]
227
+ *
228
+ * @returns {Function}
229
+ */
230
+ module.exports.checkIf = function(check, ifFn, elseReturnValue = true) {
231
+ return function(node) {
232
+ if (ifFn(node) === true) {
233
+ return check(node);
234
+ }
235
+
236
+ return elseReturnValue;
237
+ };
238
+ };
239
+
240
+ function formatTypes(types, exclusive = false) {
241
+ return types.reduce((string, type, index) => {
242
+ type = getType(type);
243
+
244
+ // first
245
+ if (index === 0) {
246
+ return `<${ type }>`;
247
+ }
248
+
249
+ // last
250
+ if (index === types.length - 1) {
251
+ return `${ string } ${ exclusive ? 'or' : 'and' } <${ type }>`;
252
+ }
253
+
254
+ return `${ string }, <${ type }>`;
255
+ }, '');
256
+ }
257
+
258
+ module.exports.formatTypes = formatTypes;
259
+
260
+ function checkProperties(node, properties, parentNode) {
261
+ return Object.entries(properties).reduce((results, property) => {
262
+ const [ propertyName, propertyChecks ] = property;
263
+
264
+ const path = getPath(node, parentNode);
265
+
266
+ const propertyValue = node.get(propertyName);
267
+
268
+ if (propertyChecks.required && !propertyValue) {
269
+ return [
270
+ ...results,
271
+ {
272
+ message: `Element of type <${ node.$type }> must have property <${ propertyName }>`,
273
+ path: path
274
+ ? [ ...path, propertyName ]
275
+ : [ propertyName ],
276
+ error: {
277
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
278
+ requiredProperty: propertyName
279
+ }
280
+ }
281
+ ];
282
+ }
283
+
284
+ if (propertyChecks.dependendRequired) {
285
+ const dependency = node.get(propertyChecks.dependendRequired);
286
+
287
+ if (dependency && !propertyValue) {
288
+ return [
289
+ ...results,
290
+ {
291
+ message: `Element of type <${ node.$type }> must have property <${ propertyName }> if it has property <${ propertyChecks.dependendRequired }>`,
292
+ path: path
293
+ ? [ ...path, propertyName ]
294
+ : [ propertyName ],
295
+ error: {
296
+ type: ERROR_TYPES.PROPERTY_DEPENDEND_REQUIRED,
297
+ dependendRequiredProperty: propertyName
298
+ }
299
+ }
300
+ ];
301
+ }
302
+ }
303
+
304
+ if (propertyChecks.type && propertyValue && (!propertyValue.$instanceOf || !propertyValue.$instanceOf(propertyChecks.type))) {
305
+ return [
306
+ ...results,
307
+ {
308
+ message: `Element of type <${ node.$type }> must have property <${ propertyName }> of type <${ propertyChecks.type }>`,
309
+ path: path
310
+ ? [ ...path, propertyName ]
311
+ : [ propertyName ],
312
+ error: {
313
+ type: ERROR_TYPES.PROPERTY_TYPE,
314
+ propertyType: propertyChecks.type
315
+ }
316
+ }
317
+ ];
318
+ }
319
+
320
+ return results;
321
+ }, []);
322
+ }
323
+
324
+ module.exports.checkProperties = checkProperties;
325
+
326
+ /**
327
+ * @example
328
+ *
329
+ * const check = hasExtensionElementsOfTypes([ 'zeebe:CalledDecision', 'zeebe:TaskDefinition' ]);
330
+ *
331
+ * check(node, parentNode);
332
+ *
333
+ * @example
334
+ *
335
+ * const check = hasExtensionElementsOfTypes([
336
+ * {
337
+ * type: 'zeebe:CalledDecision',
338
+ * properties: {
339
+ * decisionId: { required: true },
340
+ * resultVariable: { required: true }
341
+ * }
342
+ * },
343
+ * {
344
+ * type: 'zeebe:TaskDefinition',
345
+ * properties: {
346
+ * type: { required: true }
347
+ * }
348
+ * }
349
+ * ]);
350
+ *
351
+ * check(node, parentNode);
352
+ *
353
+ * @param {string[]|Object[]} types
354
+ *
355
+ * @returns {Function}
356
+ */
357
+ module.exports.hasExtensionElementsOfTypes = function(types, exclusive = false) {
358
+ return function(node, parentNode) {
359
+ const extensionElements = findExtensionElements(node, types.map(type => getType(type)));
360
+
361
+ if (!extensionElements || !extensionElements.length) {
362
+ return {
363
+ message: `Element of type <${ node.$type }> must have one or many extension elements of type ${ formatTypes(types, true) }`,
364
+ path: getPath(node, parentNode),
365
+ error: {
366
+ type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
367
+ requiredExtensionElement: getType(types[ 0 ])
368
+ }
369
+ };
370
+ }
371
+
372
+ if (exclusive && extensionElements.length > 1) {
373
+ return {
374
+ message: `Element of type <${ node.$type }> must have one extension element of type ${ formatTypes(types, true) }`,
375
+ path: getPath(node, parentNode)
376
+ };
377
+ }
378
+
379
+ const results = extensionElements.reduce((errors, extensionElement) => {
380
+ const type = types.find(type => is(extensionElement, getType(type)));
381
+
382
+ const properties = getProperties(type);
383
+
384
+ if (properties) {
385
+ return [
386
+ ...errors,
387
+ ...checkProperties(extensionElement, properties, parentNode)
388
+ ];
389
+ }
390
+
391
+ return errors;
392
+ }, []);
393
+
394
+ if (results.length === 1) {
395
+ return results[ 0 ];
396
+ } else if (results.length > 1) {
397
+ return results;
398
+ }
399
+
400
+ return true;
401
+ };
402
+ };
403
+
404
+ /**
405
+ * @example
406
+ *
407
+ * const check = hasExtensionElementOfType('zeebe:TaskDefinition');
408
+ *
409
+ * check(node, parentNode);
410
+ *
411
+ * @example
412
+ *
413
+ * const check = hasExtensionElementOfType(
414
+ * {
415
+ * type: 'zeebe:TaskDefinition',
416
+ * properties: {
417
+ * type: { required: true }
418
+ * }
419
+ * }
420
+ * );
421
+ *
422
+ * check(node, parentNode);
423
+ *
424
+ * @param {string[]|Object[]} types
425
+ *
426
+ * @returns {Function}
427
+ */
428
+ module.exports.hasExtensionElementOfType = function(type) {
429
+ return function(node, parentNode) {
430
+ const extensionElement = findExtensionElement(node, getType(type));
431
+
432
+ if (!extensionElement) {
433
+ return {
434
+ message: `Element of type <${ node.$type }> must have extension element of type <${ getType(type) }>`,
435
+ path: getPath(node, parentNode),
436
+ error: {
437
+ type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
438
+ requiredExtensionElement: getType(type)
439
+ }
440
+ };
441
+ }
442
+
443
+ const properties = getProperties(type);
444
+
445
+ if (properties) {
446
+ const results = checkProperties(extensionElement, properties, parentNode);
447
+
448
+ if (results.length === 1) {
449
+ return results[ 0 ];
450
+ } else if (results.length > 1) {
451
+ return results;
452
+ }
453
+ }
454
+
455
+ return true;
456
+ };
457
+ };
458
+
459
+ /**
460
+ * @example
461
+ *
462
+ * const check = checkError((error) => { ... });
463
+ *
464
+ * check(errorEventDefinition);
465
+ *
466
+ * @param {Function} check
467
+ *
468
+ * @returns {Function}
469
+ */
470
+ function checkError(check) {
471
+ return function(node, parentNode) {
472
+ const results = checkProperties(node, {
473
+ errorRef: {
474
+ required: true
475
+ }
476
+ }, parentNode);
477
+
478
+ if (results.length === 1) {
479
+ return results[ 0 ];
480
+ } else if (results.length > 1) {
481
+ return results;
482
+ }
483
+
484
+ const error = node.get('errorRef');
485
+
486
+ return check(error);
487
+ };
488
+ }
489
+
490
+ module.exports.checkError = checkError;
491
+
492
+ /**
493
+ * @example
494
+ *
495
+ * const check = checkEventDefinition((eventDefinition, event) => { ... });
496
+ *
497
+ * check(startEvent);
498
+ *
499
+ * @param {Function} check
500
+ *
501
+ * @returns {Function}
502
+ */
503
+ module.exports.checkEventDefinition = function(check) {
504
+ return function(node) {
505
+ const results = checkProperties(node, {
506
+ eventDefinitions: {
507
+ required: true
508
+ }
509
+ });
510
+
511
+ if (results.length === 1) {
512
+ return results[ 0 ];
513
+ } else if (results.length > 1) {
514
+ return results;
515
+ }
516
+
517
+ const eventDefinitions = node.get('eventDefinitions');
518
+
519
+ return check(eventDefinitions[ 0 ], node);
520
+ };
521
+ };
522
+
523
+ /**
524
+ * @example
525
+ *
526
+ * const check = checkFlowNode((node, parentNode) => { ... });
527
+ *
528
+ * check(serviceTask);
529
+ *
530
+ * @param {Function} check
531
+ *
532
+ * @returns {Function}
533
+ */
534
+ module.exports.checkFlowNode = function(check) {
535
+ return function(node) {
536
+ return check(node, node);
537
+ };
538
+ };
539
+
540
+ /**
541
+ * @example
542
+ *
543
+ * const check = checkMessage((message) => { ... });
544
+ *
545
+ * check(messageEventDefinition);
546
+ *
547
+ * @param {Function} check
548
+ *
549
+ * @returns {Function}
550
+ */
551
+ function checkMessage(check) {
552
+ return function(node, parentNode) {
553
+ const results = checkProperties(node, {
554
+ messageRef: {
555
+ required: true
556
+ }
557
+ }, parentNode);
558
+
559
+ if (results.length === 1) {
560
+ return results[ 0 ];
561
+ } else if (results.length > 1) {
562
+ return results;
563
+ }
564
+
565
+ const message = node.get('messageRef');
566
+
567
+ return check(message);
568
+ };
569
+ }
570
+
571
+ module.exports.checkMessage = checkMessage;
572
+
573
+ /**
574
+ * @example
575
+ *
576
+ * const check = checkLoopCharacteristics((loopCharacteristics, activity) => { ... });
577
+ *
578
+ * check(serviceTask);
579
+ *
580
+ * @param {Function} check
581
+ *
582
+ * @returns {Function}
583
+ */
584
+ module.exports.checkLoopCharacteristics = function(check) {
585
+ return function(node) {
586
+ const results = checkProperties(node, {
587
+ loopCharacteristics: {
588
+ required: true
589
+ }
590
+ });
591
+
592
+ if (results.length === 1) {
593
+ return results[ 0 ];
594
+ } else if (results.length > 1) {
595
+ return results;
596
+ }
597
+
598
+ const loopCharacteristics = node.get('loopCharacteristics');
599
+
600
+ return check(loopCharacteristics, node);
601
+ };
602
+ };
603
+
604
+ module.exports.hasErrorReference = checkError(
605
+ (node) => {
606
+ const results = checkProperties(node, {
607
+ errorCode: {
608
+ required: true
609
+ }
610
+ });
611
+
612
+ if (results.length === 1) {
613
+ return results[ 0 ];
614
+ } else if (results.length > 1) {
615
+ return results;
616
+ }
617
+
618
+ return true;
619
+ }
620
+ );
621
+
622
+ module.exports.hasMessageReference = checkMessage(
623
+ (node) => {
624
+ const results = checkProperties(node, {
625
+ name: {
626
+ required: true
627
+ }
628
+ });
629
+
630
+ if (results.length === 1) {
631
+ return results[ 0 ];
632
+ } else if (results.length > 1) {
633
+ return results;
634
+ }
635
+
636
+ return true;
637
+ }
638
+ );
639
+
640
+ function translate(result, translations) {
641
+ if (isString(result)) {
642
+ return translations[result] || result;
643
+ }
644
+
645
+ const { message } = result;
646
+
647
+ return {
648
+ ...result,
649
+ message: translations[message] || message
650
+ };
651
+ }
652
+
653
+ module.exports.withTranslations = function(check, translations) {
654
+ return function(node) {
655
+ const results = check(node);
656
+
657
+ if (isObject(results) || isString(results)) {
658
+ return translate(results, translations);
659
+ }
660
+
661
+ if (isArray(results)) {
662
+ return results.map((result) => translate(result, translations));
663
+ }
664
+
665
+ return results;
666
+ };
667
+ };
@@ -7,4 +7,4 @@
7
7
  */
8
8
  module.exports.toSemverMinor = function(string) {
9
9
  return string && string.split(/\./).slice(0, 2).join('.');
10
- }
10
+ };