bpmnlint-plugin-camunda-compat 0.12.0 → 0.13.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.
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const { omit } = require('min-dash');
2
2
 
3
3
  const calledDecisionOrTaskDefinitionConfig = require('./rules/called-decision-or-task-definition/config'),
4
- elementTypeConfig = require('./rules/element-type/config');
4
+ elementTypeConfig = require('./rules/element-type/config'),
5
+ timerConfig = require('./rules/timer/config');
5
6
 
6
7
  const camundaCloud10Rules = {
7
8
  'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud10 ],
@@ -14,6 +15,7 @@ const camundaCloud10Rules = {
14
15
  'no-template': 'error',
15
16
  'no-zeebe-properties': 'error',
16
17
  'subscription': 'error',
18
+ 'timer': [ 'error', timerConfig.camundaCloud10 ],
17
19
  'user-task-form': 'error'
18
20
  };
19
21
 
@@ -39,7 +41,8 @@ const camundaCloud80Rules = omit(camundaCloud13Rules, 'no-template');
39
41
  const camundaCloud81Rules = {
40
42
  ...omit(camundaCloud80Rules, 'no-zeebe-properties'),
41
43
  'element-type': [ 'error', elementTypeConfig.camundaCloud81 ],
42
- 'inclusive-gateway': 'error'
44
+ 'inclusive-gateway': 'error',
45
+ 'timer': [ 'error', timerConfig.camundaCloud81 ]
43
46
  };
44
47
 
45
48
  module.exports = {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "bpmnlint-plugin-camunda-compat",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A bpmnlint plug-in for Camunda Platform compatibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "all": "npm run lint && npm test",
8
+ "dev": "npm run test:watch",
8
9
  "lint": "eslint .",
9
10
  "test": "mocha test/**/*.spec.js",
10
11
  "test:watch": "npm run test -- --watch"
@@ -1,36 +1,33 @@
1
+ const camundaCloud10ElementsTaskDefinition = [
2
+ 'bpmn:ServiceTask'
3
+ ];
4
+
5
+ const camundaCloud11ElementsTaskDefinition = [
6
+ ...camundaCloud10ElementsTaskDefinition,
7
+ 'bpmn:BusinessRuleTask',
8
+ 'bpmn:ScriptTask',
9
+ 'bpmn:SendTask'
10
+ ];
11
+
12
+ const camundaCloud12ElementsTaskDefinition = [
13
+ ...camundaCloud11ElementsTaskDefinition,
14
+ 'bpmn:IntermediateThrowEvent'
15
+ ];
16
+
1
17
  module.exports = {
2
18
  camundaCloud10: {
3
- elementsTaskDefinition: [
4
- 'bpmn:ServiceTask'
5
- ]
19
+ elementsTaskDefinition: camundaCloud10ElementsTaskDefinition
6
20
  },
7
21
  camundaCloud11: {
8
- elementsTaskDefinition: [
9
- 'bpmn:BusinessRuleTask',
10
- 'bpmn:ServiceTask',
11
- 'bpmn:ScriptTask',
12
- 'bpmn:SendTask'
13
- ]
22
+ elementsTaskDefinition: camundaCloud11ElementsTaskDefinition
14
23
  },
15
24
  camundaCloud12: {
16
- elementsTaskDefinition: [
17
- 'bpmn:BusinessRuleTask',
18
- 'bpmn:IntermediateThrowEvent',
19
- 'bpmn:ServiceTask',
20
- 'bpmn:ScriptTask',
21
- 'bpmn:SendTask'
22
- ]
25
+ elementsTaskDefinition: camundaCloud12ElementsTaskDefinition
23
26
  },
24
27
  camundaCloud13: {
25
28
  elementsCalledDecision: [
26
29
  'bpmn:BusinessRuleTask'
27
30
  ],
28
- elementsTaskDefinition: [
29
- 'bpmn:BusinessRuleTask',
30
- 'bpmn:IntermediateThrowEvent',
31
- 'bpmn:ServiceTask',
32
- 'bpmn:ScriptTask',
33
- 'bpmn:SendTask'
34
- ]
31
+ elementsTaskDefinition: camundaCloud12ElementsTaskDefinition
35
32
  }
36
33
  };
@@ -94,7 +94,11 @@ const camundaCloud12EventDefinitions = {
94
94
  };
95
95
 
96
96
  const camundaCloud81EventDefinitions = {
97
- ...camundaCloud12EventDefinitions
97
+ ...camundaCloud12EventDefinitions,
98
+ 'bpmn:EndEvent': [
99
+ ...camundaCloud12EventDefinitions['bpmn:EndEvent'],
100
+ 'bpmn:TerminateEventDefinition'
101
+ ]
98
102
  };
99
103
 
100
104
  module.exports = {
@@ -0,0 +1,17 @@
1
+ const camundaCloud10Formats = [
2
+ 'iso8601'
3
+ ];
4
+
5
+ const camundaCloud81Formats = [
6
+ ...camundaCloud10Formats,
7
+ 'cron'
8
+ ];
9
+
10
+ module.exports = {
11
+ camundaCloud10: {
12
+ formats: camundaCloud10Formats
13
+ },
14
+ camundaCloud81: {
15
+ formats: camundaCloud81Formats
16
+ }
17
+ };
@@ -0,0 +1,164 @@
1
+ const {
2
+ is
3
+ } = require('bpmnlint-utils');
4
+
5
+ const {
6
+ getEventDefinition,
7
+ hasProperties,
8
+ hasProperty
9
+ } = require('../utils/element');
10
+
11
+ const { validateCronExpression } = require('../utils/cron');
12
+
13
+ const {
14
+ validateCycle: validateISO8601Cycle,
15
+ validateDate: validateISO8601Date,
16
+ validateDuration: validateISO8601Duration
17
+ } = require('../utils/iso8601');
18
+
19
+ const { reportErrors } = require('../utils/reporter');
20
+
21
+ module.exports = function(config = {}) {
22
+ const { formats = [] } = config;
23
+
24
+ function check(node, reporter) {
25
+ if (!is(node, 'bpmn:Event')) {
26
+ return;
27
+ }
28
+
29
+ const eventDefinition = getEventDefinition(node);
30
+
31
+ if (!eventDefinition || !is(eventDefinition, 'bpmn:TimerEventDefinition')) {
32
+ return;
33
+ }
34
+
35
+ let errors = checkTimePropertyExists(eventDefinition, node);
36
+
37
+ if (errors && errors.length) {
38
+ reportErrors(node, reporter, errors);
39
+
40
+ return;
41
+ }
42
+
43
+ const timeProperty = getTimeProperty(eventDefinition);
44
+
45
+ errors = checkTimePropertyNotEmpty(timeProperty, node);
46
+
47
+ if (errors && errors.length) {
48
+ reportErrors(node, reporter, errors);
49
+
50
+ return;
51
+ }
52
+
53
+ errors = checkTimePropertyCorrectFormat(eventDefinition, node, formats);
54
+
55
+ if (errors && errors.length) {
56
+ reportErrors(node, reporter, errors);
57
+ }
58
+ }
59
+
60
+ return {
61
+ check
62
+ };
63
+ };
64
+
65
+ function checkTimePropertyExists(eventDefinition, event) {
66
+ if (is(event, 'bpmn:StartEvent')) {
67
+ return hasProperty(eventDefinition, [ 'timeCycle', 'timeDate' ], event);
68
+ } else if (is(event, 'bpmn:IntermediateCatchEvent') || isInterruptingBoundaryEvent(event)) {
69
+ return hasProperties(eventDefinition, {
70
+ timeDuration: {
71
+ required: true
72
+ }
73
+ }, event);
74
+ } else if (is(event, 'bpmn:BoundaryEvent')) {
75
+ return hasProperty(eventDefinition, [ 'timeCycle', 'timeDuration' ], event);
76
+ }
77
+
78
+ return [];
79
+ }
80
+
81
+ function checkTimePropertyNotEmpty(timeProperty, event) {
82
+ return hasProperties(timeProperty, {
83
+ body: {
84
+ required: true,
85
+ }
86
+ }, event);
87
+ }
88
+
89
+ function checkTimePropertyCorrectFormat(eventDefinition, event, formats) {
90
+ const timeCycle = eventDefinition.get('timeCycle'),
91
+ timeDate = eventDefinition.get('timeDate'),
92
+ timeDuration = eventDefinition.get('timeDuration');
93
+
94
+ if (timeCycle) {
95
+ return hasProperties(timeCycle, {
96
+ body: {
97
+ allowed: cycle => validateCycle(cycle, formats)
98
+ }
99
+ }, event);
100
+ } else if (timeDate) {
101
+ return hasProperties(timeDate, {
102
+ body: {
103
+ allowed: date => validateDate(date, formats)
104
+ }
105
+ }, event);
106
+ } else if (timeDuration) {
107
+ return hasProperties(timeDuration, {
108
+ body: {
109
+ allowed: duration => validateDuration(duration, formats)
110
+ }
111
+ }, event);
112
+ }
113
+ }
114
+
115
+
116
+
117
+ // helper //////////////
118
+ function getTimeProperty(eventDefinition) {
119
+ return eventDefinition.get('timeCycle') || eventDefinition.get('timeDate') || eventDefinition.get('timeDuration');
120
+ }
121
+
122
+ function isInterruptingBoundaryEvent(event) {
123
+ return is(event, 'bpmn:BoundaryEvent') && event.get('cancelActivity') !== false;
124
+ }
125
+
126
+ function validateDate(date, formats) {
127
+ if (validateExpression(date)) {
128
+ return true;
129
+ }
130
+
131
+ if (formats.includes('iso8601') && validateISO8601Date(date)) {
132
+ return true;
133
+ }
134
+ }
135
+
136
+ function validateCycle(cycle, formats) {
137
+ if (validateExpression(cycle)) {
138
+ return true;
139
+ }
140
+
141
+ if (formats.includes('iso8601') && validateISO8601Cycle(cycle)) {
142
+ return true;
143
+ }
144
+
145
+ if (formats.includes('cron') && validateCronExpression(cycle)) {
146
+ return true;
147
+ }
148
+ }
149
+
150
+ function validateDuration(duration, formats) {
151
+ if (validateExpression(duration)) {
152
+ return true;
153
+ }
154
+
155
+ if (formats.includes('iso8601') && validateISO8601Duration(duration)) {
156
+ return true;
157
+ }
158
+ }
159
+
160
+ function validateExpression(text) {
161
+ if (text.startsWith('=')) {
162
+ return true;
163
+ }
164
+ }
@@ -0,0 +1,95 @@
1
+ const MACROS = [
2
+ '@yearly',
3
+ '@annually',
4
+ '@monthly',
5
+ '@weekly',
6
+ '@daily',
7
+ '@midnight',
8
+ '@hourly'
9
+ ];
10
+ const MACRO_REGEX = new RegExp(`^(${ MACROS.join('|') })$`);
11
+
12
+ function validateMacro(text) {
13
+ return MACRO_REGEX.test(text);
14
+ }
15
+
16
+ const ASTERISK = '\\*';
17
+ const QUESTION_MARK = '\\?';
18
+ const COMMA = ',';
19
+
20
+ const SECOND = '([0-5]?[0-9])';
21
+ const MINUTE = '([0-5]?[0-9])';
22
+ const HOUR = '([01]?[0-9]|2[0-3])';
23
+ const DAY_OF_MONTH = or(`L(-${ or('[0-2]?[0-9]', '3[0-1]') })?`, '[0-2]?[0-9]', '3[0-1]', dayOfMonthSuffix(or('L', '[0-2]?[0-9]', '3[0-1]')));
24
+
25
+ const MONTHS = [
26
+ 'JAN',
27
+ 'FEB',
28
+ 'MAR',
29
+ 'APR',
30
+ 'MAY',
31
+ 'JUN',
32
+ 'JUL',
33
+ 'AUG',
34
+ 'SEP',
35
+ 'OCT',
36
+ 'NOV',
37
+ 'DEC'
38
+ ];
39
+ const MONTH = or('[0]?[1-9]', '1[0-2]', ...MONTHS);
40
+
41
+ const WEEK_DAYS = [
42
+ 'MON',
43
+ 'TUE',
44
+ 'WED',
45
+ 'THU',
46
+ 'FRI',
47
+ 'SAT',
48
+ 'SUN'
49
+ ];
50
+ const DAY_OF_WEEK = or('[0-7]', ...WEEK_DAYS);
51
+
52
+ const SECOND_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(SECOND), optionalRange(SECOND))));
53
+ const MINUTE_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(MINUTE), optionalRange(MINUTE))));
54
+ const HOUR_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(HOUR), optionalRange(HOUR))));
55
+ const DAY_OF_MONTH_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(optionalRange(DAY_OF_MONTH)));
56
+ const MONTH_REGEX = or(ASTERISK, commaSeparated(optionalRange(MONTH)));
57
+ const DAY_OF_WEEK_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(or(optionalRange(DAY_OF_WEEK), dayOfWeekSuffix(DAY_OF_WEEK))));
58
+
59
+ const CRON_REGEX = new RegExp(`^${ SECOND_REGEX } ${ MINUTE_REGEX } ${ HOUR_REGEX } ${ DAY_OF_MONTH_REGEX } ${ MONTH_REGEX } ${ DAY_OF_WEEK_REGEX }$`, 'i');
60
+
61
+ function validateCron(value) {
62
+ return CRON_REGEX.test(value);
63
+ }
64
+
65
+ module.exports.validateCronExpression = function(value) {
66
+ return validateMacro(value) || validateCron(value);
67
+ };
68
+
69
+
70
+
71
+ // helper //////////////
72
+
73
+ function or(...patterns) {
74
+ return `(${patterns.join('|')})`;
75
+ }
76
+
77
+ function optionalRange(pattern) {
78
+ return `${pattern}(-${pattern})?`;
79
+ }
80
+
81
+ function commaSeparated(pattern) {
82
+ return `${pattern}(${COMMA}${pattern})*`;
83
+ }
84
+
85
+ function interval(pattern) {
86
+ return `${pattern}/\\d+`;
87
+ }
88
+
89
+ function dayOfMonthSuffix(pattern) {
90
+ return `${pattern}W`;
91
+ }
92
+
93
+ function dayOfWeekSuffix(pattern) {
94
+ return `${pattern}(#[1-5]|L)`;
95
+ }
@@ -1,6 +1,7 @@
1
1
  const {
2
2
  isArray,
3
3
  isDefined,
4
+ isFunction,
4
5
  isNil,
5
6
  some
6
7
  } = require('min-dash');
@@ -223,10 +224,62 @@ module.exports.hasProperties = function(node, properties, parentNode = null) {
223
224
  ];
224
225
  }
225
226
 
227
+ if (isFunction(propertyChecks.allowed) && !propertyChecks.allowed(propertyValue)) {
228
+ return [
229
+ ...results,
230
+ {
231
+ message: `Property <${ propertyName }> of element of type <${ node.$type }> must not have value of <${ truncate(propertyValue) }>`,
232
+ path: path
233
+ ? [ ...path, propertyName ]
234
+ : [ propertyName ],
235
+ error: {
236
+ type: ERROR_TYPES.PROPERTY_VALUE_NOT_ALLOWED,
237
+ node,
238
+ parentNode: parentNode == node ? null : parentNode,
239
+ property: propertyName,
240
+ }
241
+ }
242
+ ];
243
+ }
244
+
226
245
  return results;
227
246
  }, []);
228
247
  };
229
248
 
249
+ module.exports.hasProperty = function(node, types, parentNode = null) {
250
+ const typesArray = isArray(types) ? types : [ types ];
251
+
252
+ const properties = findProperties(node, typesArray);
253
+
254
+ if (properties.length !== 1) {
255
+ return [
256
+ {
257
+ message: `Element of type <${ node.$type }> must have one property of type ${ formatTypes(typesArray, true) }`,
258
+ path: getPath(node, parentNode),
259
+ error: {
260
+ type: ERROR_TYPES.PROPERTY_REQUIRED,
261
+ node,
262
+ parentNode: parentNode == node ? null : parentNode,
263
+ requiredProperty: types
264
+ }
265
+ }
266
+ ];
267
+ }
268
+
269
+ return [];
270
+ };
271
+
272
+ function findProperties(node, types) {
273
+ const properties = [];
274
+ for (const type of types) {
275
+ if (isDefined(node.get(type))) {
276
+ properties.push(node.get(type));
277
+ }
278
+ }
279
+
280
+ return properties;
281
+ }
282
+
230
283
  module.exports.hasExtensionElement = function(node, types, parentNode = null) {
231
284
  const typesArray = isArray(types) ? types : [ types ];
232
285
 
@@ -281,4 +334,10 @@ module.exports.isExactly = isExactly;
281
334
 
282
335
  module.exports.isAnyExactly = function(node, types) {
283
336
  return some(types, (type) => isExactly(node, type));
284
- };
337
+ };
338
+
339
+ function truncate(string, maxLength = 10) {
340
+ const stringified = `${ string }`;
341
+
342
+ return stringified.length > maxLength ? `${ stringified.slice(0, maxLength) }...` : stringified;
343
+ }
@@ -6,5 +6,6 @@ module.exports.ERROR_TYPES = Object.freeze({
6
6
  PROPERTY_NOT_ALLOWED: 'propertyNotAllowed',
7
7
  PROPERTY_REQUIRED: 'propertyRequired',
8
8
  PROPERTY_TYPE_NOT_ALLOWED: 'propertyTypeNotAllowed',
9
- PROPERTY_VALUE_DUPLICATED: 'propertyValueDuplicated'
9
+ PROPERTY_VALUE_DUPLICATED: 'propertyValueDuplicated',
10
+ PROPERTY_VALUE_NOT_ALLOWED: 'propertyValueNotAllowed',
10
11
  });
@@ -0,0 +1,23 @@
1
+ const ISO_DATE_REGEX = /^\d{4}(-\d\d){2}T(\d\d:){2}\d\d(Z|([+-]\d\d:\d\d)(\[\w+\/\w+\])?)?$/;
2
+ const ISO_DURATION = 'P(?!$)(\\d+(\\.\\d+)?[Yy])?(\\d+(\\.\\d+)?[Mm])?(\\d+(\\.\\d+)?[Dd])?(T(?!$)(\\d+(\\.\\d+)?[Hh])?(\\d+(\\.\\d+)?[Mm])?(\\d+(\\.\\d+)?[Ss])?)?$';
3
+ const ISO_DURATION_REGEX = new RegExp(`^${ISO_DURATION}$`);
4
+ const ISO_CYCLE = `R(-1|\\d+)?/${ISO_DURATION}`;
5
+ const ISO_CYCLE_REGEX = new RegExp(`^${ISO_CYCLE}$`);
6
+
7
+ module.exports = {
8
+ validateCycle,
9
+ validateDate,
10
+ validateDuration
11
+ };
12
+
13
+ function validateCycle(value) {
14
+ return ISO_CYCLE_REGEX.test(value);
15
+ }
16
+
17
+ function validateDate(value) {
18
+ return ISO_DATE_REGEX.test(value);
19
+ }
20
+
21
+ function validateDuration(value) {
22
+ return ISO_DURATION_REGEX.test(value);
23
+ }