bpmnlint 10.2.3 → 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/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 +140 -0
- package/rules/helper.js +31 -1
- package/rules/label-required.js +0 -4
- package/rules/no-implicit-end.js +40 -1
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.1",
|
|
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,140 @@
|
|
|
1
|
+
const {
|
|
2
|
+
is,
|
|
3
|
+
isAny
|
|
4
|
+
} = require('bpmnlint-utils');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A rule that verifies that global elements are properly used.
|
|
9
|
+
*
|
|
10
|
+
* Currently recognized global elements are:
|
|
11
|
+
*
|
|
12
|
+
* * `bpmn:Error`
|
|
13
|
+
* * `bpmn:Escalation`
|
|
14
|
+
* * `bpmn:Message`
|
|
15
|
+
* * `bpmn:Signal`
|
|
16
|
+
*
|
|
17
|
+
* For each of these elements proper usage implies:
|
|
18
|
+
*
|
|
19
|
+
* * element must have a name
|
|
20
|
+
* * element is referenced by at least one element
|
|
21
|
+
* * there exists only a single element per type with a given name
|
|
22
|
+
*/
|
|
23
|
+
module.exports = function() {
|
|
24
|
+
|
|
25
|
+
function check(node, reporter) {
|
|
26
|
+
|
|
27
|
+
if (!is(node, 'bpmn:Definitions')) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rootElements = getRootElements(node);
|
|
32
|
+
|
|
33
|
+
const referencingElements = getReferencingElements(node);
|
|
34
|
+
|
|
35
|
+
rootElements.forEach(rootElement => {
|
|
36
|
+
if (!hasName(rootElement)) {
|
|
37
|
+
reporter.report(rootElement.id, 'Element is missing name');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!isReferenced(rootElement, referencingElements)) {
|
|
41
|
+
reporter.report(rootElement.id, 'Element is unused');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!isUnique(rootElement, rootElements)) {
|
|
45
|
+
reporter.report(rootElement.id, 'Element name is not unique');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
check
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// helpers /////////////////////////////
|
|
56
|
+
|
|
57
|
+
function getRootElements(definitions) {
|
|
58
|
+
return definitions.rootElements.filter(node => isAny(node, [ 'bpmn:Error', 'bpmn:Escalation', 'bpmn:Message', 'bpmn:Signal' ]));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getReferencingElements(definitions) {
|
|
62
|
+
const referencingElements = [];
|
|
63
|
+
|
|
64
|
+
function traverse(element) {
|
|
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));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (is(element, 'bpmn:Collaboration') && element.get('messageFlows').length) {
|
|
78
|
+
element.get('messageFlows').forEach(traverse);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (isAny(element, [
|
|
82
|
+
'bpmn:MessageFlow',
|
|
83
|
+
'bpmn:ReceiveTask',
|
|
84
|
+
'bpmn:SendTask'
|
|
85
|
+
])) {
|
|
86
|
+
referencingElements.push(element);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
traverse(definitions);
|
|
91
|
+
|
|
92
|
+
return referencingElements;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasName(event) {
|
|
96
|
+
return (
|
|
97
|
+
event.name?.trim() !== ''
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
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
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
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
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isUnique(rootElement, rootElements) {
|
|
136
|
+
return (
|
|
137
|
+
rootElements.filter(otherRootElement => is(otherRootElement, rootElement.$type) && rootElement.name === otherRootElement.name).length === 1
|
|
138
|
+
);
|
|
139
|
+
}
|
|
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;
|
package/rules/label-required.js
CHANGED
package/rules/no-implicit-end.js
CHANGED
|
@@ -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
|
+
};
|