bpmnlint 8.3.2 → 9.1.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/config/all.js CHANGED
@@ -13,6 +13,7 @@ const allRules = [
13
13
  'no-implicit-end',
14
14
  'no-implicit-start',
15
15
  'no-inclusive-gateway',
16
+ 'no-overlapping-elements',
16
17
  'single-blank-start-event',
17
18
  'single-event-definition',
18
19
  'start-event-required',
@@ -14,6 +14,7 @@ module.exports = {
14
14
  'no-implicit-end': 'error',
15
15
  'no-implicit-start': 'error',
16
16
  'no-inclusive-gateway': 'error',
17
+ 'no-overlapping-elements': 'warn',
17
18
  'single-blank-start-event': 'error',
18
19
  'single-event-definition': 'error',
19
20
  'start-event-required': 'error',
package/lib/linter.js CHANGED
@@ -7,6 +7,8 @@ const categoryMap = {
7
7
  3: 'info'
8
8
  };
9
9
 
10
+ const ruleErrorCategory = 'rule-error';
11
+
10
12
 
11
13
  function Linter(options = {}) {
12
14
 
@@ -69,8 +71,8 @@ Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
69
71
 
70
72
  return [
71
73
  {
72
- message: 'Rule error: ' + e.message,
73
- category: 'error'
74
+ message: e.message,
75
+ category: ruleErrorCategory
74
76
  }
75
77
  ];
76
78
  }
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": "8.3.2",
4
+ "version": "9.1.0",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [
7
7
  "bpmnlint",
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@bpmn-io/bpmnlint-generate-docs-images": "^0.4.0",
54
- "chai": "^4.3.4",
55
- "eslint": "^8.42.0",
54
+ "chai": "^4.3.7",
55
+ "eslint": "^8.44.0",
56
56
  "eslint-plugin-bpmn-io": "^1.0.0",
57
57
  "esm": "^3.2.25",
58
58
  "execa": "^5.1.1",
@@ -0,0 +1,174 @@
1
+ const {
2
+ is
3
+ } = require('bpmnlint-utils');
4
+
5
+
6
+ /**
7
+ * Rule that checks if two elements overlap except:
8
+ * - Boundary events overlap their host
9
+ * - Child elements overlap / are on top of their parent (e.g., elements within a subProcess)
10
+ */
11
+ module.exports = function() {
12
+
13
+ function check(node, reporter) {
14
+ if (!is(node, 'bpmn:Definitions')) {
15
+ return;
16
+ }
17
+
18
+ const rootElements = node.rootElements || [];
19
+ const elementsToReport = new Set();
20
+ const elementsOutsideToReport = new Set();
21
+ const diObjects = getAllDiObjects(node);
22
+ const processElementsParentDiMap = new Map(); // map with sub/process as key and its parent boundary di object
23
+
24
+ rootElements
25
+ .filter(element => is(element, 'bpmn:Collaboration'))
26
+ .forEach(collaboration => {
27
+ const participants = collaboration.participants || [];
28
+ checkElementsArray(participants, elementsToReport, diObjects);
29
+
30
+ participants.forEach(participant => {
31
+ processElementsParentDiMap.set(participant.processRef, diObjects.get(participant));
32
+ });
33
+ });
34
+
35
+ rootElements
36
+ .filter(element => is(element, 'bpmn:Process'))
37
+ .forEach(process => {
38
+ const parentDi = processElementsParentDiMap.get(process) || {};
39
+ checkProcess(process, elementsToReport, elementsOutsideToReport, diObjects, parentDi);
40
+ });
41
+
42
+ // report elements
43
+ elementsToReport.forEach(element => reporter.report(element.id, 'Element overlaps with other element'));
44
+ elementsOutsideToReport.forEach(element => reporter.report(element.id, 'Element is outside of parent boundary'));
45
+ }
46
+
47
+ return {
48
+ check: check
49
+ };
50
+ };
51
+
52
+ // helpers /////////////////
53
+
54
+ /**
55
+ * Recursively check subprocesses in a process
56
+ * @param {Object} node Process or SubProcess
57
+ * @param {Set} elementsToReport
58
+ * @param {Set} elementsOutsideToReport
59
+ * @param {Map} diObjects
60
+ */
61
+ function checkProcess(node, elementsToReport, elementsOutsideToReport, diObjects, parentDi) {
62
+
63
+ // check child elements for overlap
64
+ const flowElements = node.flowElements || [];
65
+ checkElementsArray(flowElements, elementsToReport, diObjects);
66
+
67
+ // check child elements outside parent boundary
68
+ // TODO: Skipped DataSoreReferences for now
69
+ flowElements.filter(element => !is(element, 'bpmn:DataStoreReference')).forEach(element => {
70
+ if (isOutsideParentBoundary(diObjects.get(element).bounds, parentDi.bounds)) {
71
+ elementsOutsideToReport.add(element);
72
+ }
73
+ });
74
+
75
+ // check subprocesses
76
+ const subProcesses = flowElements.filter(element => is(element, 'bpmn:SubProcess'));
77
+ subProcesses.forEach(subProcess => {
78
+ const subProcessDi = diObjects.get(subProcess) || {};
79
+ const subProcessParentBoundary = subProcessDi.isExpanded ? subProcessDi : {};
80
+ checkProcess(subProcess, elementsToReport, elementsOutsideToReport, diObjects, subProcessParentBoundary);
81
+ });
82
+ }
83
+
84
+ /**
85
+ * @param {Array} elements
86
+ * @param {Set} elementsToReport
87
+ */
88
+ function checkElementsArray(elements, elementsToReport, diObjects) {
89
+ for (let i = 0; i < elements.length - 1; i++) {
90
+ const element = elements[i];
91
+ for (let j = i + 1; j < elements.length; j++) {
92
+ const element2 = elements[j];
93
+
94
+ if (!diObjects.has(element) || !diObjects.has(element2)) {
95
+ continue;
96
+ }
97
+
98
+ // ignore if Boundary events overlap their host
99
+ // but still check if they overlap other elements
100
+ if (element.attachedToRef === element2 || element2.attachedToRef === element) {
101
+ continue;
102
+ }
103
+
104
+ if (isCollision(diObjects.get(element).bounds, diObjects.get(element2).bounds)) {
105
+ elementsToReport.add(element);
106
+ elementsToReport.add(element2);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Check if child element is outside of parent boundary
114
+ */
115
+ function isOutsideParentBoundary(childBounds, parentBounds) {
116
+ if (!isValidShapeElement(childBounds) || !isValidShapeElement(parentBounds)) {
117
+ return false;
118
+ }
119
+
120
+ const isTopLeftCornerInside = childBounds.x >= parentBounds.x && childBounds.y >= parentBounds.y;
121
+ const isBottomRightCornerInside = childBounds.x + childBounds.width <= parentBounds.x + parentBounds.width && childBounds.y + childBounds.height <= parentBounds.y + parentBounds.height;
122
+ const isInside = isTopLeftCornerInside && isBottomRightCornerInside;
123
+
124
+ return !isInside;
125
+ }
126
+
127
+ /**
128
+ * Check if two rectangle shapes collides
129
+ */
130
+ function isCollision(firstBounds, secondBounds) {
131
+ if (!isValidShapeElement(firstBounds) || !isValidShapeElement(secondBounds)) {
132
+ return false;
133
+ }
134
+
135
+ const collisionX = firstBounds.x + firstBounds.width >= secondBounds.x && secondBounds.x + secondBounds.width >= firstBounds.x;
136
+ const collisionY = firstBounds.y + firstBounds.height >= secondBounds.y && secondBounds.y + secondBounds.height >= firstBounds.y;
137
+
138
+ // collision on both axis
139
+ return collisionX && collisionY;
140
+ }
141
+
142
+ /**
143
+ * Checks if shape bounds has all necessary values for collision check
144
+ */
145
+ function isValidShapeElement(bounds) {
146
+ return !!bounds && is(bounds, 'dc:Bounds') &&
147
+ typeof (bounds.x) === 'number' &&
148
+ typeof (bounds.y) === 'number' &&
149
+ typeof (bounds.width) === 'number' &&
150
+ typeof (bounds.height) === 'number';
151
+ }
152
+
153
+ /**
154
+ * Get all di object as one map object
155
+ * @param {Object} node bpmn:Definitions
156
+ * @returns {Map<Object, Object>} map of di objects with element as key
157
+ */
158
+ function getAllDiObjects(node) {
159
+ const diObjects = new Map();
160
+ const diagrams = node.diagrams || [];
161
+
162
+ diagrams
163
+ .filter(diagram => !!diagram.plane)
164
+ .forEach(diagram => {
165
+ const planeElements = diagram.plane.planeElement || [];
166
+ planeElements
167
+ .filter(planeElement => !!planeElement.bpmnElement)
168
+ .forEach(planeElement => {
169
+ diObjects.set(planeElement.bpmnElement, planeElement);
170
+ });
171
+ });
172
+
173
+ return diObjects;
174
+ }