bpmnlint 10.3.0 → 11.0.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/README.md CHANGED
@@ -72,6 +72,8 @@ import NodeResolver from 'bpmnlint/lib/resolver/node-resolver';
72
72
 
73
73
  import BpmnModdle from 'bpmn-moddle';
74
74
 
75
+ const moddle = new BpmnModdle();
76
+
75
77
  const linter = new Linter({
76
78
  config: {
77
79
  extends: 'bpmnlint:recommended'
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": "11.0.0",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [
7
7
  "bpmnlint",
@@ -23,12 +23,12 @@
23
23
  }
24
24
  ],
25
25
  "engines": {
26
- "node": ">= 16"
26
+ "node": ">= 20"
27
27
  },
28
28
  "license": "MIT",
29
29
  "scripts": {
30
30
  "all": "run-s lint test-coverage",
31
- "lint": "eslint . --ext js,mjs",
31
+ "lint": "eslint .",
32
32
  "dev": "npm test -- --watch",
33
33
  "test": "mocha --exclude 'test/integration/*/**' 'test/**/*.*js'",
34
34
  "test-coverage": "nyc --reporter=lcov --reporter=html npm test",
@@ -40,26 +40,25 @@
40
40
  "dependencies": {
41
41
  "@bpmn-io/moddle-utils": "^0.2.1",
42
42
  "ansi-colors": "^4.1.3",
43
- "bpmn-moddle": "^8.1.0",
43
+ "bpmn-moddle": "^9.0.1",
44
44
  "bpmnlint-utils": "^1.1.1",
45
45
  "cli-table": "^0.3.11",
46
46
  "color-support": "^1.1.3",
47
- "min-dash": "^4.2.1",
47
+ "min-dash": "^4.2.2",
48
48
  "mri": "^1.2.0",
49
49
  "pluralize": "^7.0.0",
50
50
  "tiny-glob": "^0.2.9"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@bpmn-io/bpmnlint-generate-docs-images": "^0.4.0",
54
- "bpmnlint": ".",
55
- "chai": "^4.4.1",
56
- "eslint": "^8.57.0",
57
- "eslint-plugin-bpmn-io": "^1.0.0",
58
- "execa": "^8.0.1",
54
+ "chai": "^4.5.0",
55
+ "eslint": "^9.17.0",
56
+ "eslint-plugin-bpmn-io": "^2.0.2",
57
+ "execa": "^9.5.2",
59
58
  "install-local": "^3.0.1",
60
- "mocha": "^10.4.0",
59
+ "mocha": "^11.0.1",
61
60
  "npm-run-all": "^4.1.5",
62
- "nyc": "^15.1.0",
61
+ "nyc": "^17.1.0",
63
62
  "strip-indent": "^3.0.0"
64
63
  },
65
64
  "files": [
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
 
@@ -103,13 +103,13 @@ function getAllBpmnElements(rootElements) {
103
103
  */
104
104
  function getAllDiBpmnReferences(definitionsNode) {
105
105
  return flatten(
106
- definitionsNode.diagrams.map((diagram) => {
106
+ definitionsNode.get('diagrams').map((diagram) => {
107
107
 
108
108
  const diElements = diagram.plane.planeElement || [];
109
109
 
110
110
  return diElements.map((element) => {
111
111
 
112
- return element.bpmnElement.id;
112
+ return element.bpmnElement?.id;
113
113
  });
114
114
  })
115
115
  );
@@ -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
+ };
@@ -108,7 +108,15 @@ function checkElementsArray(elements, elementsToReport, diObjects) {
108
108
  continue;
109
109
  }
110
110
 
111
- if (isCollision(diObjects.get(element).bounds, diObjects.get(element2).bounds)) {
111
+ const bounds1 = diObjects.get(element)?.bounds;
112
+ const bounds2 = diObjects.get(element2)?.bounds;
113
+
114
+ // ignore if an element doesn't have bounds
115
+ if (!bounds1 || !bounds2) {
116
+ continue;
117
+ }
118
+
119
+ if (isCollision(bounds1, bounds2)) {
112
120
  elementsToReport.add(element);
113
121
  elementsToReport.add(element2);
114
122
  }