bpmnlint 10.2.2 → 10.3.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 +1 -0
- package/config/recommended.js +1 -0
- package/lib/testers/rule-tester.d.ts +37 -0
- package/lib/traverse.js +6 -1
- package/lib/types.d.ts +5 -0
- package/package.json +6 -6
- package/rules/global.js +117 -0
- package/rules/no-overlapping-elements.js +16 -14
package/config/all.js
CHANGED
package/config/recommended.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type TestFn = (...any) => any;
|
|
2
|
+
|
|
3
|
+
export type ModdleElement = any;
|
|
4
|
+
|
|
5
|
+
export type ValidRuleTest = {
|
|
6
|
+
config?: any,
|
|
7
|
+
it?: TestFn,
|
|
8
|
+
moddleElement: ModdleElement,
|
|
9
|
+
name?: string
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type InvalidReport = {
|
|
13
|
+
id: string,
|
|
14
|
+
message: string
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type InvalidRuleTest = {
|
|
18
|
+
config?: any,
|
|
19
|
+
it?: TestFn,
|
|
20
|
+
moddleElement: ModdleElement,
|
|
21
|
+
name?: string,
|
|
22
|
+
report: InvalidReport | InvalidReport[]
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type RuleTests = {
|
|
26
|
+
valid?: ValidRuleTest[],
|
|
27
|
+
invalid?: InvalidRuleTest[]
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Verify a rule.
|
|
32
|
+
*
|
|
33
|
+
* @param ruleName
|
|
34
|
+
* @param rule the rule implementation
|
|
35
|
+
* @param tests the tests to verify
|
|
36
|
+
*/
|
|
37
|
+
export function verify(ruleName: string, rule: any, tests: RuleTests): void;
|
package/lib/traverse.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef { import('./types.js').EnterFn } EnterFn
|
|
3
|
+
* @typedef { import('./types.js').LeaveFn } LeaveFn
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Traverse a moddle tree, depth first from top to bottom
|
|
3
8
|
* and call the passed visitor fn.
|
|
4
9
|
*
|
|
5
10
|
* @param {ModdleElement} element
|
|
6
|
-
* @param {{ enter?:
|
|
11
|
+
* @param {{ enter?: EnterFn; leave?: LeaveFn }} options
|
|
7
12
|
*/
|
|
8
13
|
module.exports = function traverse(element, options) {
|
|
9
14
|
|
package/lib/types.d.ts
ADDED
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.
|
|
4
|
+
"version": "10.3.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bpmnlint",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@bpmn-io/moddle-utils": "^0.2.1",
|
|
42
42
|
"ansi-colors": "^4.1.3",
|
|
43
|
-
"bpmn-moddle": "^8.0
|
|
43
|
+
"bpmn-moddle": "^8.1.0",
|
|
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.
|
|
47
|
+
"min-dash": "^4.2.1",
|
|
48
48
|
"mri": "^1.2.0",
|
|
49
49
|
"pluralize": "^7.0.0",
|
|
50
50
|
"tiny-glob": "^0.2.9"
|
|
@@ -52,12 +52,12 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@bpmn-io/bpmnlint-generate-docs-images": "^0.4.0",
|
|
54
54
|
"bpmnlint": ".",
|
|
55
|
-
"chai": "^4.
|
|
56
|
-
"eslint": "^8.
|
|
55
|
+
"chai": "^4.4.1",
|
|
56
|
+
"eslint": "^8.57.0",
|
|
57
57
|
"eslint-plugin-bpmn-io": "^1.0.0",
|
|
58
58
|
"execa": "^8.0.1",
|
|
59
59
|
"install-local": "^3.0.1",
|
|
60
|
-
"mocha": "^10.
|
|
60
|
+
"mocha": "^10.4.0",
|
|
61
61
|
"npm-run-all": "^4.1.5",
|
|
62
62
|
"nyc": "^15.1.0",
|
|
63
63
|
"strip-indent": "^3.0.0"
|
package/rules/global.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isAny, is
|
|
3
|
+
} = require('bpmnlint-utils');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A rule that verifies that global elements are properly used.
|
|
8
|
+
*
|
|
9
|
+
* Currently recognized global elements are:
|
|
10
|
+
*
|
|
11
|
+
* * `bpmn:Error`
|
|
12
|
+
* * `bpmn:Escalation`
|
|
13
|
+
* * `bpmn:Signal`
|
|
14
|
+
* * `bpmn:Message`
|
|
15
|
+
*
|
|
16
|
+
* For each of these elements proper usage implies:
|
|
17
|
+
*
|
|
18
|
+
* * element must have a name
|
|
19
|
+
* * element is used (referenced) from event definitions
|
|
20
|
+
* * there exists only a single element per type with a given name
|
|
21
|
+
*/
|
|
22
|
+
module.exports = function() {
|
|
23
|
+
|
|
24
|
+
function check(node, reporter) {
|
|
25
|
+
|
|
26
|
+
if (!is(node, 'bpmn:Definitions')) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const events = getEvents(node);
|
|
31
|
+
const eventDefinitions = getEventDefinitions(node);
|
|
32
|
+
|
|
33
|
+
events.forEach(event => {
|
|
34
|
+
if (!hasName(event)) {
|
|
35
|
+
reporter.report(event.id, 'Element is missing name');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!isReferenced(event, eventDefinitions)) {
|
|
39
|
+
reporter.report(event.id, 'Element is unused');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isUnique(event, events)) {
|
|
43
|
+
reporter.report(event.id, 'Element name is not unique');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
check
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// helpers /////////////////////////////
|
|
54
|
+
|
|
55
|
+
function getEvents(definition) {
|
|
56
|
+
return definition.rootElements.filter(node => isAny(node, [ 'bpmn:Error', 'bpmn:Escalation', 'bpmn:Message', 'bpmn:Signal' ]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getEventDefinitions(definition) {
|
|
60
|
+
const eventDefinitions = [];
|
|
61
|
+
|
|
62
|
+
function traverse(element) {
|
|
63
|
+
if (element.rootElements) {
|
|
64
|
+
element.rootElements.forEach(traverse);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (element.flowElements) {
|
|
68
|
+
element.flowElements.forEach(traverse);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (element.eventDefinitions) {
|
|
72
|
+
element.eventDefinitions.forEach(eventDefinition => eventDefinitions.push(eventDefinition));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
traverse(definition);
|
|
77
|
+
return eventDefinitions;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasName(event) {
|
|
81
|
+
return (
|
|
82
|
+
event.name?.trim() !== ''
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
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
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (is(event, 'bpmn:Escalation')) {
|
|
94
|
+
return (
|
|
95
|
+
eventDefinitions.some(node => is(node, 'bpmn:EscalationEventDefinition') && event.id === node.escalationRef?.id)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (is(event, 'bpmn:Message')) {
|
|
100
|
+
return (
|
|
101
|
+
eventDefinitions.some(node => is(node, 'bpmn:MessageEventDefinition') && event.id === node.messageRef?.id)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (is(event, 'bpmn:Signal')) {
|
|
106
|
+
return (
|
|
107
|
+
eventDefinitions.some(node => is(node, 'bpmn:SignalEventDefinition') && event.id === node.signalRef?.id)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isUnique(event, events) {
|
|
113
|
+
return (
|
|
114
|
+
events.filter(node => is(node, event.$type) && event.name === node.name).length === 1
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
@@ -60,24 +60,29 @@ module.exports = function() {
|
|
|
60
60
|
*/
|
|
61
61
|
function checkProcess(node, elementsToReport, elementsOutsideToReport, diObjects, parentDi) {
|
|
62
62
|
|
|
63
|
-
// check child elements for overlap
|
|
64
63
|
const flowElements = node.flowElements || [];
|
|
65
|
-
checkElementsArray(flowElements, elementsToReport, diObjects);
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
// TODO: Skipped DataSoreReferences for now
|
|
69
|
-
flowElements.filter(element => !is(element, 'bpmn:DataStoreReference')).forEach(element => {
|
|
65
|
+
const flowElementsWithDi = flowElements.filter(element => diObjects.has(element));
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
67
|
+
// check child elements for overlap
|
|
68
|
+
checkElementsArray(flowElementsWithDi, elementsToReport, diObjects);
|
|
74
69
|
|
|
75
|
-
|
|
70
|
+
// check child elements outside parent boundary
|
|
71
|
+
//
|
|
72
|
+
// * data objects do not have a visual representation
|
|
73
|
+
// * for historical reasons data store references may be
|
|
74
|
+
// outside of parent boundaries
|
|
75
|
+
//
|
|
76
|
+
flowElementsWithDi.forEach(element => {
|
|
77
|
+
if (
|
|
78
|
+
!is(element, 'bpmn:DataStoreReference') &&
|
|
79
|
+
isOutsideParentBoundary(diObjects.get(element).bounds, parentDi.bounds)
|
|
80
|
+
) {
|
|
76
81
|
elementsOutsideToReport.add(element);
|
|
77
82
|
}
|
|
78
83
|
});
|
|
79
84
|
|
|
80
|
-
//
|
|
85
|
+
// recurse into subprocesses
|
|
81
86
|
const subProcesses = flowElements.filter(element => is(element, 'bpmn:SubProcess'));
|
|
82
87
|
subProcesses.forEach(subProcess => {
|
|
83
88
|
const subProcessDi = diObjects.get(subProcess) || {};
|
|
@@ -93,13 +98,10 @@ function checkProcess(node, elementsToReport, elementsOutsideToReport, diObjects
|
|
|
93
98
|
function checkElementsArray(elements, elementsToReport, diObjects) {
|
|
94
99
|
for (let i = 0; i < elements.length - 1; i++) {
|
|
95
100
|
const element = elements[i];
|
|
101
|
+
|
|
96
102
|
for (let j = i + 1; j < elements.length; j++) {
|
|
97
103
|
const element2 = elements[j];
|
|
98
104
|
|
|
99
|
-
if (!diObjects.has(element) || !diObjects.has(element2)) {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
105
|
// ignore if Boundary events overlap their host
|
|
104
106
|
// but still check if they overlap other elements
|
|
105
107
|
if (element.attachedToRef === element2 || element2.attachedToRef === element) {
|