bpmnlint-plugin-camunda-compat 0.2.0 → 0.5.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,635 @@
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 }} {{ executionPlatformVersion }}`,
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 }} {{ executionPlatformVersion }}`,
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 property <${ propertyChecks.dependendRequired }> is set`,
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
+ * retries: { required: true }
348
+ * }
349
+ * }
350
+ * ]);
351
+ *
352
+ * check(node, parentNode);
353
+ *
354
+ * @param {string[]|Object[]} types
355
+ *
356
+ * @returns {Function}
357
+ */
358
+ module.exports.hasExtensionElementsOfTypes = function(types, exclusive = false) {
359
+ return function(node, parentNode) {
360
+ const extensionElements = findExtensionElements(node, types.map(type => getType(type)));
361
+
362
+ if (!extensionElements || !extensionElements.length) {
363
+ return {
364
+ message: `Element of type <${ node.$type }> must have have at least one ${ formatTypes(types, true) } extension element`,
365
+ path: getPath(node, parentNode),
366
+ error: {
367
+ type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
368
+ requiredExtensionElement: getType(types[ 0 ])
369
+ }
370
+ };
371
+ }
372
+
373
+ if (exclusive && extensionElements.length > 1) {
374
+ return {
375
+ message: `Element of type <${ node.$type }> must have have either one ${ formatTypes(types, true) } extension element`,
376
+ path: getPath(node, parentNode)
377
+ };
378
+ }
379
+
380
+ const results = extensionElements.reduce((errors, extensionElement) => {
381
+ const type = types.find(type => is(extensionElement, getType(type)));
382
+
383
+ const properties = getProperties(type);
384
+
385
+ if (properties) {
386
+ return [
387
+ ...errors,
388
+ ...checkProperties(extensionElement, properties, parentNode)
389
+ ];
390
+ }
391
+
392
+ return errors;
393
+ }, []);
394
+
395
+ if (results.length === 1) {
396
+ return results[ 0 ];
397
+ } else if (results.length > 1) {
398
+ return results;
399
+ }
400
+
401
+ return true;
402
+ };
403
+ };
404
+
405
+ /**
406
+ * @example
407
+ *
408
+ * const check = hasExtensionElementOfType('zeebe:TaskDefinition');
409
+ *
410
+ * check(node, parentNode);
411
+ *
412
+ * @example
413
+ *
414
+ * const check = hasExtensionElementOfType(
415
+ * {
416
+ * type: 'zeebe:TaskDefinition',
417
+ * properties: {
418
+ * type: { required: true },
419
+ * retries: { required: true }
420
+ * }
421
+ * }
422
+ * );
423
+ *
424
+ * check(node, parentNode);
425
+ *
426
+ * @param {string[]|Object[]} types
427
+ *
428
+ * @returns {Function}
429
+ */
430
+ module.exports.hasExtensionElementOfType = function(type) {
431
+ return function(node, parentNode) {
432
+ const extensionElement = findExtensionElement(node, getType(type));
433
+
434
+ if (!extensionElement) {
435
+ return {
436
+ message: `Element of type <${ node.$type }> must have <${getType(
437
+ type
438
+ )}> extension element`,
439
+ path: getPath(node, parentNode),
440
+ error: {
441
+ type: ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED,
442
+ requiredExtensionElement: getType(type)
443
+ }
444
+ };
445
+ }
446
+
447
+ const properties = getProperties(type);
448
+
449
+ if (properties) {
450
+ const results = checkProperties(extensionElement, properties, parentNode);
451
+
452
+ if (results.length === 1) {
453
+ return results[ 0 ];
454
+ } else if (results.length > 1) {
455
+ return results;
456
+ }
457
+ }
458
+
459
+ return true;
460
+ };
461
+ };
462
+
463
+ /**
464
+ * @example
465
+ *
466
+ * const check = checkError((error) => { ... });
467
+ *
468
+ * check(errorEventDefinition);
469
+ *
470
+ * @param {Function} check
471
+ *
472
+ * @returns {Function}
473
+ */
474
+ function checkError(check) {
475
+ return function(node, parentNode) {
476
+ const results = checkProperties(node, {
477
+ errorRef: {
478
+ required: true
479
+ }
480
+ }, parentNode);
481
+
482
+ if (results.length === 1) {
483
+ return results[ 0 ];
484
+ } else if (results.length > 1) {
485
+ return results;
486
+ }
487
+
488
+ const error = node.get('errorRef');
489
+
490
+ return check(error);
491
+ };
492
+ }
493
+
494
+ module.exports.checkError = checkError;
495
+
496
+ /**
497
+ * @example
498
+ *
499
+ * const check = checkEventDefinition((eventDefinition, event) => { ... });
500
+ *
501
+ * check(startEvent);
502
+ *
503
+ * @param {Function} check
504
+ *
505
+ * @returns {Function}
506
+ */
507
+ module.exports.checkEventDefinition = function(check) {
508
+ return function(node) {
509
+ const results = checkProperties(node, {
510
+ eventDefinitions: {
511
+ required: true
512
+ }
513
+ });
514
+
515
+ if (results.length === 1) {
516
+ return results[ 0 ];
517
+ } else if (results.length > 1) {
518
+ return results;
519
+ }
520
+
521
+ const eventDefinitions = node.get('eventDefinitions');
522
+
523
+ return check(eventDefinitions[ 0 ], node);
524
+ };
525
+ };
526
+
527
+ /**
528
+ * @example
529
+ *
530
+ * const check = checkFlowNode((node, parentNode) => { ... });
531
+ *
532
+ * check(serviceTask);
533
+ *
534
+ * @param {Function} check
535
+ *
536
+ * @returns {Function}
537
+ */
538
+ module.exports.checkFlowNode = function(check) {
539
+ return function(node) {
540
+ return check(node, node);
541
+ };
542
+ };
543
+
544
+ /**
545
+ * @example
546
+ *
547
+ * const check = checkMessage((message) => { ... });
548
+ *
549
+ * check(messageEventDefinition);
550
+ *
551
+ * @param {Function} check
552
+ *
553
+ * @returns {Function}
554
+ */
555
+ module.exports.checkMessage = function(check) {
556
+ return function(node, parentNode) {
557
+ const results = checkProperties(node, {
558
+ messageRef: {
559
+ required: true
560
+ }
561
+ }, parentNode);
562
+
563
+ if (results.length === 1) {
564
+ return results[ 0 ];
565
+ } else if (results.length > 1) {
566
+ return results;
567
+ }
568
+
569
+ const message = node.get('messageRef');
570
+
571
+ return check(message);
572
+ };
573
+ };
574
+
575
+ /**
576
+ * @example
577
+ *
578
+ * const check = checkLoopCharacteristics((loopCharacteristics, activity) => { ... });
579
+ *
580
+ * check(serviceTask);
581
+ *
582
+ * @param {Function} check
583
+ *
584
+ * @returns {Function}
585
+ */
586
+ module.exports.checkLoopCharacteristics = function(check) {
587
+ return function(node) {
588
+ const results = checkProperties(node, {
589
+ loopCharacteristics: {
590
+ required: true
591
+ }
592
+ });
593
+
594
+ if (results.length === 1) {
595
+ return results[ 0 ];
596
+ } else if (results.length > 1) {
597
+ return results;
598
+ }
599
+
600
+ const loopCharacteristics = node.get('loopCharacteristics');
601
+
602
+ return check(loopCharacteristics, node);
603
+ };
604
+ };
605
+
606
+ module.exports.hasErrorReference = checkError(() => true);
607
+
608
+ function translate(result, translations) {
609
+ if (isString(result)) {
610
+ return translations[result] || result;
611
+ }
612
+
613
+ const { message } = result;
614
+
615
+ return {
616
+ ...result,
617
+ message: translations[message] || message
618
+ };
619
+ }
620
+
621
+ module.exports.withTranslations = function(check, translations) {
622
+ return function(node) {
623
+ const results = check(node);
624
+
625
+ if (isObject(results) || isString(results)) {
626
+ return translate(results, translations);
627
+ }
628
+
629
+ if (isArray(results)) {
630
+ return results.map((result) => translate(result, translations));
631
+ }
632
+
633
+ return results;
634
+ };
635
+ };
@@ -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
+ };