bpmnlint 10.3.0 → 10.3.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bpmnlint",
3
3
  "description": "Validate your BPMN diagrams based on configurable lint rules",
4
- "version": "10.3.0",
4
+ "version": "10.3.1",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [
7
7
  "bpmnlint",
package/rules/global.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const {
2
- isAny, is
2
+ is,
3
+ isAny
3
4
  } = require('bpmnlint-utils');
4
5
 
5
6
 
@@ -10,13 +11,13 @@ const {
10
11
  *
11
12
  * * `bpmn:Error`
12
13
  * * `bpmn:Escalation`
13
- * * `bpmn:Signal`
14
14
  * * `bpmn:Message`
15
+ * * `bpmn:Signal`
15
16
  *
16
17
  * For each of these elements proper usage implies:
17
18
  *
18
19
  * * element must have a name
19
- * * element is used (referenced) from event definitions
20
+ * * element is referenced by at least one element
20
21
  * * there exists only a single element per type with a given name
21
22
  */
22
23
  module.exports = function() {
@@ -27,20 +28,21 @@ module.exports = function() {
27
28
  return false;
28
29
  }
29
30
 
30
- const events = getEvents(node);
31
- const eventDefinitions = getEventDefinitions(node);
31
+ const rootElements = getRootElements(node);
32
32
 
33
- events.forEach(event => {
34
- if (!hasName(event)) {
35
- reporter.report(event.id, 'Element is missing name');
33
+ const referencingElements = getReferencingElements(node);
34
+
35
+ rootElements.forEach(rootElement => {
36
+ if (!hasName(rootElement)) {
37
+ reporter.report(rootElement.id, 'Element is missing name');
36
38
  }
37
39
 
38
- if (!isReferenced(event, eventDefinitions)) {
39
- reporter.report(event.id, 'Element is unused');
40
+ if (!isReferenced(rootElement, referencingElements)) {
41
+ reporter.report(rootElement.id, 'Element is unused');
40
42
  }
41
43
 
42
- if (!isUnique(event, events)) {
43
- reporter.report(event.id, 'Element name is not unique');
44
+ if (!isUnique(rootElement, rootElements)) {
45
+ reporter.report(rootElement.id, 'Element name is not unique');
44
46
  }
45
47
  });
46
48
 
@@ -52,29 +54,42 @@ module.exports = function() {
52
54
 
53
55
  // helpers /////////////////////////////
54
56
 
55
- function getEvents(definition) {
56
- return definition.rootElements.filter(node => isAny(node, [ 'bpmn:Error', 'bpmn:Escalation', 'bpmn:Message', 'bpmn:Signal' ]));
57
+ function getRootElements(definitions) {
58
+ return definitions.rootElements.filter(node => isAny(node, [ 'bpmn:Error', 'bpmn:Escalation', 'bpmn:Message', 'bpmn:Signal' ]));
57
59
  }
58
60
 
59
- function getEventDefinitions(definition) {
60
- const eventDefinitions = [];
61
+ function getReferencingElements(definitions) {
62
+ const referencingElements = [];
61
63
 
62
64
  function traverse(element) {
63
- if (element.rootElements) {
64
- element.rootElements.forEach(traverse);
65
+ if (is(element, 'bpmn:Definitions') && element.get('rootElements').length) {
66
+ element.get('rootElements').forEach(traverse);
67
+ }
68
+
69
+ if (is(element, 'bpmn:FlowElementsContainer') && element.get('flowElements').length) {
70
+ element.get('flowElements').forEach(traverse);
71
+ }
72
+
73
+ if (is(element, 'bpmn:Event') && element.get('eventDefinitions').length) {
74
+ element.get('eventDefinitions').forEach(eventDefinition => referencingElements.push(eventDefinition));
65
75
  }
66
76
 
67
- if (element.flowElements) {
68
- element.flowElements.forEach(traverse);
77
+ if (is(element, 'bpmn:Collaboration') && element.get('messageFlows').length) {
78
+ element.get('messageFlows').forEach(traverse);
69
79
  }
70
80
 
71
- if (element.eventDefinitions) {
72
- element.eventDefinitions.forEach(eventDefinition => eventDefinitions.push(eventDefinition));
81
+ if (isAny(element, [
82
+ 'bpmn:MessageFlow',
83
+ 'bpmn:ReceiveTask',
84
+ 'bpmn:SendTask'
85
+ ])) {
86
+ referencingElements.push(element);
73
87
  }
74
88
  }
75
89
 
76
- traverse(definition);
77
- return eventDefinitions;
90
+ traverse(definitions);
91
+
92
+ return referencingElements;
78
93
  }
79
94
 
80
95
  function hasName(event) {
@@ -83,35 +98,43 @@ module.exports = function() {
83
98
  );
84
99
  }
85
100
 
86
- function isReferenced(event, eventDefinitions) {
87
- if (is(event, 'bpmn:Error')) {
88
- return (
89
- eventDefinitions.some(node => is(node, 'bpmn:ErrorEventDefinition') && event.id === node.errorRef?.id)
90
- );
101
+ function isReferenced(rootElement, referencingElements) {
102
+ if (is(rootElement, 'bpmn:Error')) {
103
+ return referencingElements.some(referencingElement => {
104
+ return is(referencingElement, 'bpmn:ErrorEventDefinition')
105
+ && rootElement.get('id') === referencingElement.get('errorRef')?.get('id');
106
+ });
91
107
  }
92
108
 
93
- if (is(event, 'bpmn:Escalation')) {
94
- return (
95
- eventDefinitions.some(node => is(node, 'bpmn:EscalationEventDefinition') && event.id === node.escalationRef?.id)
96
- );
109
+ if (is(rootElement, 'bpmn:Escalation')) {
110
+ return referencingElements.some(referencingElement => {
111
+ return is(referencingElement, 'bpmn:EscalationEventDefinition')
112
+ && rootElement.get('id') === referencingElement.get('escalationRef')?.get('id');
113
+ });
97
114
  }
98
115
 
99
- if (is(event, 'bpmn:Message')) {
100
- return (
101
- eventDefinitions.some(node => is(node, 'bpmn:MessageEventDefinition') && event.id === node.messageRef?.id)
102
- );
116
+ if (is(rootElement, 'bpmn:Message')) {
117
+ return referencingElements.some(referencingElement => {
118
+ return isAny(referencingElement, [
119
+ 'bpmn:MessageEventDefinition',
120
+ 'bpmn:MessageFlow',
121
+ 'bpmn:ReceiveTask',
122
+ 'bpmn:SendTask'
123
+ ]) && rootElement.get('id') === referencingElement.get('messageRef')?.get('id');
124
+ });
103
125
  }
104
126
 
105
- if (is(event, 'bpmn:Signal')) {
106
- return (
107
- eventDefinitions.some(node => is(node, 'bpmn:SignalEventDefinition') && event.id === node.signalRef?.id)
108
- );
127
+ if (is(rootElement, 'bpmn:Signal')) {
128
+ return referencingElements.some(referencingElement => {
129
+ return is(referencingElement, 'bpmn:SignalEventDefinition')
130
+ && rootElement.get('id') === referencingElement.get('signalRef')?.get('id');
131
+ });
109
132
  }
110
133
  }
111
134
 
112
- function isUnique(event, events) {
135
+ function isUnique(rootElement, rootElements) {
113
136
  return (
114
- events.filter(node => is(node, event.$type) && event.name === node.name).length === 1
137
+ rootElements.filter(otherRootElement => is(otherRootElement, rootElement.$type) && rootElement.name === otherRootElement.name).length === 1
115
138
  );
116
139
  }
117
140
  };
package/rules/helper.js CHANGED
@@ -28,4 +28,34 @@ function disallowNodeType(type) {
28
28
 
29
29
  }
30
30
 
31
- module.exports.disallowNodeType = disallowNodeType;
31
+ module.exports.disallowNodeType = disallowNodeType;
32
+
33
+ /**
34
+ * Find a parent for the given element
35
+ *
36
+ * @param {ModdleElement} node
37
+ *
38
+ * @param {String} type
39
+ *
40
+ * @return {ModdleElement} element
41
+ */
42
+
43
+ function findParent(node, type) {
44
+ if (!node) {
45
+ return null;
46
+ }
47
+
48
+ const parent = node.$parent;
49
+
50
+ if (!parent) {
51
+ return node;
52
+ }
53
+
54
+ if (is(parent, type)) {
55
+ return parent;
56
+ }
57
+
58
+ return findParent(parent, type);
59
+ }
60
+
61
+ module.exports.findParent = findParent;
@@ -23,10 +23,6 @@ module.exports = function() {
23
23
  return;
24
24
  }
25
25
 
26
- if (is(node, 'bpmn:BoundaryEvent')) {
27
- return;
28
- }
29
-
30
26
  // ignore sub-processes
31
27
  if (is(node, 'bpmn:SubProcess')) {
32
28
 
@@ -3,6 +3,9 @@ const {
3
3
  isAny
4
4
  } = require('bpmnlint-utils');
5
5
 
6
+ const {
7
+ findParent
8
+ } = require('./helper');
6
9
 
7
10
  /**
8
11
  * A rule that checks that an element is not an implicit end (token sink).
@@ -17,6 +20,34 @@ module.exports = function() {
17
20
  );
18
21
  }
19
22
 
23
+ function isCompensationEvent(node) {
24
+ const eventDefinitions = node.eventDefinitions || [];
25
+
26
+ return eventDefinitions.length && eventDefinitions.every(
27
+ definition => is(definition, 'bpmn:CompensateEventDefinition')
28
+ );
29
+ }
30
+
31
+ function hasCompensationActivity(node) {
32
+ const parent = findParent(node, 'bpmn:Process');
33
+
34
+ const artifacts = parent.artifacts || [];
35
+
36
+ return artifacts.some((element) => {
37
+ if (!is(element, 'bpmn:Association')) {
38
+ return false;
39
+ }
40
+
41
+ const source = element.sourceRef;
42
+
43
+ return source.id === node.id;
44
+ });
45
+ }
46
+
47
+ function isForCompensation(node) {
48
+ return node.isForCompensation;
49
+ }
50
+
20
51
  function isImplicitEnd(node) {
21
52
  const outgoing = node.outgoing || [];
22
53
 
@@ -32,6 +63,14 @@ module.exports = function() {
32
63
  return false;
33
64
  }
34
65
 
66
+ if (is(node, 'bpmn:BoundaryEvent') && isCompensationEvent(node) && hasCompensationActivity(node)) {
67
+ return false;
68
+ }
69
+
70
+ if (is(node, 'bpmn:Task') && isForCompensation(node)) {
71
+ return false;
72
+ }
73
+
35
74
  return outgoing.length === 0;
36
75
  }
37
76
 
@@ -47,4 +86,4 @@ module.exports = function() {
47
86
  }
48
87
 
49
88
  return { check };
50
- };
89
+ };