bpmnlint 9.0.0 → 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/bin/bpmnlint.js +0 -0
- package/config/all.js +1 -0
- package/config/recommended.js +1 -0
- package/package.json +3 -3
- package/rules/no-overlapping-elements.js +174 -0
package/bin/bpmnlint.js
CHANGED
|
File without changes
|
package/config/all.js
CHANGED
package/config/recommended.js
CHANGED
|
@@ -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/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": "9.
|
|
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.
|
|
55
|
-
"eslint": "^8.
|
|
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
|
+
}
|