bpmnlint-plugin-camunda-compat 0.4.0 → 0.6.1

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.
@@ -1,7 +1,9 @@
1
1
  const {
2
+ every,
2
3
  isArray,
3
- isNil,
4
+ isObject,
4
5
  isString,
6
+ some
5
7
  } = require('min-dash');
6
8
 
7
9
  const {
@@ -11,13 +13,31 @@ const {
11
13
 
12
14
  const { toSemverMinor } = require('./engine-profile');
13
15
 
16
+ const { ERROR_TYPES } = require('./element');
17
+
18
+ const { getTypeString } = require('./type');
19
+
20
+ /**
21
+ * Factory function for rules. Returns a rule for a given execution platform,
22
+ * execution platform version and an array of checks. Must be run on
23
+ * bpmn:Definitions node. Returns early without traversing further if execution
24
+ * platform or execution platform version doesn't match. Returns early if node
25
+ * is not a bpmn:FlowElement or bpmn:FlowElementContainer.
26
+ *
27
+ * @param {string} ruleExecutionPlatform
28
+ * @param {string} ruleExecutionPlatformVersion
29
+ * @param {(Object|string)[]} checks
30
+ * @param {string} executionPlatformLabel
31
+ *
32
+ * @returns {Function}
33
+ */
14
34
  module.exports.createRule = function(ruleExecutionPlatform, ruleExecutionPlatformVersion, checks, executionPlatformLabel) {
15
35
  return () => {
16
36
  return {
17
37
  check: (node, reporter) => {
18
38
  if (is(node, 'bpmn:Definitions')) {
19
- executionPlatform = node.get('modeler:executionPlatform');
20
- executionPlatformVersion = node.get('modeler:executionPlatformVersion');
39
+ const executionPlatform = node.get('modeler:executionPlatform');
40
+ const executionPlatformVersion = node.get('modeler:executionPlatformVersion');
21
41
 
22
42
  if (!executionPlatform
23
43
  || executionPlatform !== ruleExecutionPlatform
@@ -29,20 +49,75 @@ module.exports.createRule = function(ruleExecutionPlatform, ruleExecutionPlatfor
29
49
  return;
30
50
  }
31
51
 
32
- const result = checkNode(node, checks);
52
+ let results = checkNode(node, checks);
33
53
 
34
- if (result === false) {
35
- reporter.report(node.get('id') || '', `Element of type <${ node.$type }> not supported by ${ executionPlatformLabel || ruleExecutionPlatform } ${ toSemverMinor(ruleExecutionPlatformVersion) }`);
54
+ if (results === true) {
55
+ return;
36
56
  }
37
57
 
38
- if (isString(result)) {
39
- reporter.report(node.get('id') || '', `Element of type <${ result }> not supported by ${ executionPlatformLabel || ruleExecutionPlatform } ${ toSemverMinor(ruleExecutionPlatformVersion) }`);
58
+ const id = node.get('id') || '';
59
+
60
+ const label = getLabel(node);
61
+
62
+ if (results === false) {
63
+ const message = getMessage(node, ruleExecutionPlatform, ruleExecutionPlatformVersion, executionPlatformLabel);
64
+
65
+ let options = {
66
+ error: {
67
+ type: ERROR_TYPES.ELEMENT_TYPE,
68
+ element: node.$type
69
+ }
70
+ };
71
+
72
+ if (label) {
73
+ options = {
74
+ ...options,
75
+ label
76
+ };
77
+ }
78
+
79
+ reporter.report(id, message, options);
80
+ } else {
81
+ if (!isArray(results)) {
82
+ results = [ results ];
83
+ }
84
+
85
+ results.forEach((result) => {
86
+ if (isString(result)) {
87
+ result = { message: result };
88
+ }
89
+
90
+ let {
91
+ message,
92
+ ...rest
93
+ } = result;
94
+
95
+ message = addExecutionPlatform(message, executionPlatformLabel || `${ ruleExecutionPlatform } ${ toSemverMinor(ruleExecutionPlatformVersion) }`);
96
+
97
+ let options = {
98
+ ...rest
99
+ };
100
+
101
+ if (label) {
102
+ options = {
103
+ ...options,
104
+ label
105
+ };
106
+ }
107
+
108
+ reporter.report(id, message, options);
109
+ });
40
110
  }
41
111
  }
42
112
  };
43
113
  };
44
114
  };
45
115
 
116
+ /**
117
+ * Create no-op rule that always returns false resulting in early return.
118
+ *
119
+ * @returns {Function}
120
+ */
46
121
  module.exports.createNoopRule = function() {
47
122
  return () => {
48
123
  return {
@@ -52,146 +127,198 @@ module.exports.createNoopRule = function() {
52
127
  };
53
128
 
54
129
  /**
130
+ * Run checks on a node. Return true if at least one of the checks returns true.
131
+ * Otherwise return all errors or false.
132
+ *
55
133
  * @param {ModdleElement} node
56
134
  * @param {Array<Function>} checks
57
135
  *
58
- * @returns boolean|String
136
+ * @returns {boolean|Object|Object[]|string|string[]}
59
137
  */
60
138
  function checkNode(node, checks) {
61
- return checks.reduce((previousResult, check) => {
62
- if (previousResult === true) {
63
- return previousResult;
64
- }
139
+ const results = checks.map((check) => {
65
140
 
66
141
  // (1) check using type only
67
142
  if (isString(check)) {
68
- return is(node, check) || previousResult;
143
+ return isExactly(node, check);
69
144
  }
70
145
 
71
146
  const { type } = check;
72
147
 
73
148
  // (2) check using function only
74
149
  if (!type) {
75
- return check.check(node) || previousResult;
150
+ return check.check(node);
76
151
  }
77
152
 
78
153
  // (3) check using type and function
79
- if (!is(node, type)) {
80
- return previousResult;
154
+ if (!isExactly(node, type)) {
155
+ return false;
81
156
  }
82
157
 
83
- return check.check(node) || previousResult;
84
- }, false);
158
+ return check.check(node);
159
+ });
160
+
161
+ return results.find((result) => result === true) || getErrors(results);
85
162
  }
86
163
 
164
+ module.exports.checkNode = checkNode;
165
+
87
166
  /**
88
- * If every check returns true return true.
89
- * Otherwise return the first false or string returned by a check.
167
+ * Create function that runs checks on a node. Return true if all checks return
168
+ * true. Otherwise return all errors or false.
90
169
  *
91
170
  * @param {Array<Function>} checks
92
171
  *
93
- * @returns {boolean|String}
172
+ * @returns {boolean|Object|Object[]|string|string[]}
94
173
  */
95
174
  module.exports.checkEvery = function(...checks) {
96
175
  return function(node) {
97
- return checks.reduce((previousResult, check) => {
98
- if (!isNil(previousResult) && previousResult !== true) {
99
- return previousResult;
100
- }
176
+ const results = checks.map((check) => check(node));
101
177
 
102
- const result = check(node);
103
-
104
- return result;
105
- }, null);
178
+ return every(results, result => result === true) || getErrors(results);
106
179
  };
107
- }
180
+ };
108
181
 
109
182
  /**
110
- * If some check returns true return true.
111
- * Otherwise return the first false or string returned by a check.
183
+ * Create function that runs checks on a node. Return true if at least one of
184
+ * the checks returns true. Otherwise return all errors or false.
112
185
  *
113
186
  * @param {Array<Function>} checks
114
187
  *
115
- * @returns {boolean|String}
188
+ * @returns {boolean|Object|Object[]|string|string[]}
116
189
  */
117
190
  module.exports.checkSome = function(...checks) {
118
191
  return function(node) {
119
- return checks.reduce((previousResult, check) => {
120
- if (previousResult === true) {
121
- return previousResult;
122
- }
192
+ const results = checks.map((check) => check(node));
123
193
 
124
- const result = check(node);
194
+ return some(results, result => result === true) || getErrors(results);
195
+ };
196
+ };
197
+
198
+ /**
199
+ * Replace check for element of type.
200
+ *
201
+ * @param {(Object|string)[]} checks
202
+ * @param {string} type
203
+ * @param {Function} check
204
+ *
205
+ * @returns {(Object|string)[]}
206
+ */
207
+ module.exports.replaceCheck = function(checks, type, check) {
208
+ return replaceChecks(checks, [
209
+ {
210
+ type,
211
+ check
212
+ }
213
+ ]);
214
+ };
125
215
 
126
- if (isNil(previousResult) || result === true) {
127
- return result;
216
+ function replaceChecks(checks, replacements) {
217
+ return checks.map((check) => {
218
+ if (isString(check)) {
219
+ const replacement = replacements.find((replacement) => check === replacement.type);
220
+
221
+ if (replacement) {
222
+ return {
223
+ check: replacement.check,
224
+ type: check
225
+ };
128
226
  }
227
+ }
129
228
 
130
- return previousResult;
131
- }, null);
132
- };
133
- }
229
+ if (check.type) {
230
+ const replacement = replacements.find((replacement) => check.type === replacement.type);
134
231
 
135
- module.exports.hasEventDefinition = function(node) {
136
- const eventDefinitions = node.get('eventDefinitions');
232
+ if (replacement) {
233
+ return {
234
+ ...check,
235
+ check: replacement.check
236
+ };
237
+ }
238
+ }
137
239
 
138
- return eventDefinitions && eventDefinitions.length === 1;
240
+ return check;
241
+ });
139
242
  }
140
243
 
141
- module.exports.hasNoEventDefinition = function(node) {
142
- const eventDefinitions = node.get('eventDefinitions');
244
+ module.exports.replaceChecks = replaceChecks;
143
245
 
144
- return !eventDefinitions || !eventDefinitions.length || `${ node.$type} (${ eventDefinitions[ 0 ].$type })`;
246
+ function addExecutionPlatform(string, executionPlatform) {
247
+ return string
248
+ .replace('{{ executionPlatform }}', executionPlatform);
145
249
  }
146
250
 
147
- module.exports.hasEventDefinitionOfType = function(types) {
148
- return function(node) {
149
- if (!isArray(types)) {
150
- types = [ types ];
251
+ /**
252
+ * Return all errors or false.
253
+ *
254
+ * @param {(boolean|Object|string)[]} result
255
+ *
256
+ * @returns {boolean|(Object|string)[]}
257
+ */
258
+ function getErrors(results) {
259
+ const errors = results.reduce((errors, result) => {
260
+ if (isArray(result)) {
261
+ return [
262
+ ...errors,
263
+ ...result
264
+ ];
151
265
  }
152
266
 
153
- const eventDefinitions = node.get('eventDefinitions');
154
-
155
- if (!eventDefinitions || eventDefinitions.length !== 1) {
156
- return false;
267
+ if (isObject(result) || isString(result)) {
268
+ return [
269
+ ...errors,
270
+ result
271
+ ];
157
272
  }
158
273
 
159
- const eventDefinition = eventDefinitions[ 0 ];
160
-
161
- return isAny(eventDefinition, types) || `${ node.$type} (${ eventDefinition.$type })`;
162
- };
163
- }
274
+ return errors;
275
+ }, []);
164
276
 
165
- module.exports.hasLoopCharacteristics = function(node) {
166
- const loopCharacteristics = node.get('loopCharacteristics');
277
+ if (errors.length === 1) {
278
+ return errors[ 0 ];
279
+ } else if (errors.length > 1) {
280
+ return errors;
281
+ }
167
282
 
168
- return !!loopCharacteristics;
283
+ return false;
169
284
  }
170
285
 
171
- module.exports.hasNoLoopCharacteristics = function(node) {
172
- const loopCharacteristics = node.get('loopCharacteristics');
286
+ function isExactly(node, type) {
287
+ const { $model } = node;
173
288
 
174
- return !loopCharacteristics || `${ node.$type} (${ loopCharacteristics.$type })`;
289
+ return $model.getType(node.$type) === $model.getType(type);
175
290
  }
176
291
 
177
- module.exports.hasNoLanes = function(node) {
178
- const laneSets = node.get('laneSets');
179
-
180
- return !laneSets || !laneSets.length || `${ node.$type} (bpmn:LaneSet)`;
292
+ function getIndefiniteArticle(type) {
293
+ if ([
294
+ 'Error',
295
+ 'Escalation',
296
+ 'Event',
297
+ 'Intermediate',
298
+ 'Undefined'
299
+ ].includes(type.split(' ')[ 0 ])) {
300
+ return 'An';
301
+ }
302
+
303
+ return 'A';
181
304
  }
182
305
 
183
- module.exports.hasLoopCharacteristicsOfType = function(type) {
184
- return function(node) {
185
- const loopCharacteristics = node.get('loopCharacteristics');
306
+ function getMessage(node, executionPlatform, executionPlatformVersion, label) {
307
+ const type = getTypeString(node);
186
308
 
187
- if (!loopCharacteristics) {
188
- return false;
189
- }
309
+ const indefiniteArticle = getIndefiniteArticle(type);
190
310
 
191
- return is(loopCharacteristics, type) || `${ node.$type} (${ loopCharacteristics.$type })`;
192
- };
311
+ return addExecutionPlatform(`${ indefiniteArticle } <${ type }> is not supported by {{ executionPlatform }}`, label || `${ executionPlatform } ${ toSemverMinor(executionPlatformVersion) }`);
193
312
  }
194
313
 
195
- module.exports.isNotBpmn = function(node) {
196
- return !is(node, 'bpmn:BaseElement');
314
+ function getLabel(node) {
315
+ if (is(node, 'bpmn:Group')) {
316
+ const categoryValueRef = node.get('categoryValueRef');
317
+
318
+ return categoryValueRef && categoryValueRef.get('value');
319
+ } else if (is(node, 'bpmn:TextAnnotation')) {
320
+ return node.get('text');
321
+ }
322
+
323
+ return node.get('name');
197
324
  }
@@ -0,0 +1,47 @@
1
+ function getEventDefinition(node) {
2
+ const eventDefinitions = node.get('eventDefinitions');
3
+
4
+ return eventDefinitions && eventDefinitions[0];
5
+ }
6
+
7
+ function getEventDefinitionPrefix(eventDefinition) {
8
+ const localType = getLocalType(eventDefinition);
9
+
10
+ return localType.replace('EventDefinition', '');
11
+ }
12
+
13
+ function getLocalType(node) {
14
+ const { $descriptor } = node;
15
+
16
+ const { localName } = $descriptor.ns;
17
+
18
+ return localName;
19
+ }
20
+
21
+ function formatTypeString(string) {
22
+ return string.replace(/([a-z])([A-Z])/g, '$1 $2').trim();
23
+ }
24
+
25
+ module.exports.getTypeString = function(node) {
26
+ let type = getLocalType(node);
27
+
28
+ const eventDefinition = getEventDefinition(node);
29
+
30
+ if (eventDefinition) {
31
+ type = `${ getEventDefinitionPrefix(eventDefinition) }${ type }`;
32
+ } else if ([
33
+ 'BoundaryEvent',
34
+ 'EndEvent',
35
+ 'IntermediateCatchEvent',
36
+ 'IntermediateThrowEvent',
37
+ 'StartEvent'
38
+ ].includes(type)) {
39
+ type = `Undefined ${ type }`;
40
+ }
41
+
42
+ if (type === 'Task') {
43
+ type = 'Undefined Task';
44
+ }
45
+
46
+ return formatTypeString(type);
47
+ };