bpmnlint 7.0.1 → 7.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/CHANGELOG.md +19 -0
- package/README.md +5 -2
- package/bin/bpmnlint.js +11 -0
- package/config/all.js +2 -1
- package/config/recommended.js +2 -1
- package/lib/support/compile-config.js +5 -5
- package/lib/test-rule.js +15 -1
- package/lib/testers/rule-tester.js +4 -4
- package/lib/traverse.js +26 -21
- package/package.json +2 -1
- package/rules/no-bpmndi.js +129 -0
- package/rules/no-disconnected.js +39 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ All notable changes to [bpmnlint](https://github.com/bpmn-io/bpmnlint) are docum
|
|
|
6
6
|
|
|
7
7
|
___Note:__ Yet to be released changes appear here._
|
|
8
8
|
|
|
9
|
+
## 7.3.0
|
|
10
|
+
|
|
11
|
+
* `FEAT`: support `--version` command line flag ([#65](https://github.com/bpmn-io/bpmnlint/pull/65))
|
|
12
|
+
* `FEAT`: allow rule testers to provide custom `it`
|
|
13
|
+
* `FIX`: make `no-bpmndi` handle missing `LaneSet#lanes` gracefully ([#63](https://github.com/bpmn-io/bpmnlint/issues/63), [#66](https://github.com/bpmn-io/bpmnlint/pull/66))
|
|
14
|
+
|
|
15
|
+
## 7.2.1
|
|
16
|
+
|
|
17
|
+
* `FIX`: exclude compensation boundary and activity in `no-disconnected` check ([#60](https://github.com/bpmn-io/bpmnlint/pull/60))
|
|
18
|
+
|
|
19
|
+
## 7.2.0
|
|
20
|
+
|
|
21
|
+
* `FEAT`: add `no-bpmndi` rule ([#55](https://github.com/bpmn-io/bpmnlint/issues/55))
|
|
22
|
+
|
|
23
|
+
## 7.1.0
|
|
24
|
+
|
|
25
|
+
* `FEAT`: allow rules to skip sub-tree traversal ([#53](https://github.com/bpmn-io/bpmnlint/issues/53))
|
|
26
|
+
* `FEAT`: allow rules to hook into node `enter` and `leave` ([#52](https://github.com/bpmn-io/bpmnlint/issues/52))
|
|
27
|
+
|
|
9
28
|
## 7.0.1
|
|
10
29
|
|
|
11
30
|
* `CHORE`: drop debug log statement
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# bpmnlint
|
|
2
2
|
|
|
3
|
-
[](https://github.com/bpmn-io/bpmnlint/actions?query=workflow%3ACI)
|
|
4
4
|
|
|
5
5
|
Validate your BPMN diagrams based on configurable lint rules.
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Installation
|
|
9
9
|
|
|
10
10
|
Install the utility via [npm](https://www.npmjs.com/package/bpmnlint):
|
|
11
11
|
|
|
@@ -13,6 +13,9 @@ Install the utility via [npm](https://www.npmjs.com/package/bpmnlint):
|
|
|
13
13
|
npm install -g bpmnlint
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
16
19
|
Validate your diagrams via the commandline:
|
|
17
20
|
|
|
18
21
|
```sh
|
package/bin/bpmnlint.js
CHANGED
|
@@ -146,6 +146,12 @@ function errorAndExit(...args) {
|
|
|
146
146
|
process.exit(1);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
function showVersionAndExit() {
|
|
150
|
+
console.log(require('../package.json').version);
|
|
151
|
+
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
function infoAndExit(...args) {
|
|
150
156
|
console.log(...args);
|
|
151
157
|
|
|
@@ -314,6 +320,7 @@ async function run() {
|
|
|
314
320
|
const {
|
|
315
321
|
help,
|
|
316
322
|
init,
|
|
323
|
+
version,
|
|
317
324
|
config: configOverridePath,
|
|
318
325
|
_: files
|
|
319
326
|
} = mri(process.argv.slice(2), {
|
|
@@ -323,6 +330,10 @@ async function run() {
|
|
|
323
330
|
}
|
|
324
331
|
});
|
|
325
332
|
|
|
333
|
+
if (version) {
|
|
334
|
+
return showVersionAndExit();
|
|
335
|
+
}
|
|
336
|
+
|
|
326
337
|
if (help) {
|
|
327
338
|
return infoAndExit(HELP_STRING);
|
|
328
339
|
}
|
package/config/all.js
CHANGED
|
@@ -4,6 +4,7 @@ const allRules = [
|
|
|
4
4
|
'event-sub-process-typed-start-event',
|
|
5
5
|
'fake-join',
|
|
6
6
|
'label-required',
|
|
7
|
+
'no-bpmndi',
|
|
7
8
|
'no-complex-gateway',
|
|
8
9
|
'no-disconnected',
|
|
9
10
|
'no-duplicate-sequence-flows',
|
|
@@ -24,4 +25,4 @@ module.exports = {
|
|
|
24
25
|
|
|
25
26
|
return rules;
|
|
26
27
|
}, {})
|
|
27
|
-
};
|
|
28
|
+
};
|
package/config/recommended.js
CHANGED
|
@@ -5,6 +5,7 @@ module.exports = {
|
|
|
5
5
|
'event-sub-process-typed-start-event': 'error',
|
|
6
6
|
'fake-join': 'warn',
|
|
7
7
|
'label-required': 'error',
|
|
8
|
+
'no-bpmndi': 'error',
|
|
8
9
|
'no-complex-gateway': 'error',
|
|
9
10
|
'no-disconnected': 'error',
|
|
10
11
|
'no-duplicate-sequence-flows': 'error',
|
|
@@ -17,4 +18,4 @@ module.exports = {
|
|
|
17
18
|
'sub-process-blank-start-event': 'error',
|
|
18
19
|
'superfluous-gateway': 'warning'
|
|
19
20
|
}
|
|
20
|
-
};
|
|
21
|
+
};
|
|
@@ -34,7 +34,7 @@ async function compileConfig(config) {
|
|
|
34
34
|
const serializedRules = JSON.stringify(enabledRules, null, ' ');
|
|
35
35
|
|
|
36
36
|
const preambleCode = `
|
|
37
|
-
|
|
37
|
+
const cache = {};
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* A resolver that caches rules and configuration as part of the bundle,
|
|
@@ -61,15 +61,15 @@ Resolver.prototype.resolveConfig = function(pkg, configName) {
|
|
|
61
61
|
);
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
const resolver = new Resolver();
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
const rules = ${serializedRules};
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const config = {
|
|
69
69
|
rules: rules
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
const bundle = {
|
|
73
73
|
resolver: resolver,
|
|
74
74
|
config: config
|
|
75
75
|
};
|
package/lib/test-rule.js
CHANGED
|
@@ -15,6 +15,20 @@ class Reporter {
|
|
|
15
15
|
|
|
16
16
|
module.exports = function testRule({ moddleRoot, rule }) {
|
|
17
17
|
const reporter = new Reporter({ rule, moddleRoot });
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
const check = rule.check;
|
|
20
|
+
|
|
21
|
+
const enter = check && check.enter || check;
|
|
22
|
+
const leave = check && check.leave;
|
|
23
|
+
|
|
24
|
+
if (!enter && !leave) {
|
|
25
|
+
throw new Error('no check implemented');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
traverse(moddleRoot, {
|
|
29
|
+
enter: enter ? (node) => enter(node, reporter) : null,
|
|
30
|
+
leave: leave ? (node) => leave(node, reporter) : null
|
|
31
|
+
});
|
|
32
|
+
|
|
19
33
|
return reporter.messages;
|
|
20
34
|
};
|
|
@@ -33,9 +33,9 @@ function verify(ruleName, rule, testCases) {
|
|
|
33
33
|
|
|
34
34
|
describe('should lint valid', function() {
|
|
35
35
|
|
|
36
|
-
testCases.valid.forEach(({ moddleElement }, idx) => (
|
|
36
|
+
testCases.valid.forEach(({ moddleElement, it: _it }, idx) => (
|
|
37
37
|
|
|
38
|
-
it(`test case #${idx + 1}`, function() {
|
|
38
|
+
(_it || it)(`test case #${idx + 1}`, function() {
|
|
39
39
|
return (
|
|
40
40
|
Promise.resolve(moddleElement)
|
|
41
41
|
.then(moddleRoot => {
|
|
@@ -54,9 +54,9 @@ function verify(ruleName, rule, testCases) {
|
|
|
54
54
|
|
|
55
55
|
describe('should lint invalid', function() {
|
|
56
56
|
|
|
57
|
-
testCases.invalid.forEach(({ moddleElement, report }, idx) => (
|
|
57
|
+
testCases.invalid.forEach(({ moddleElement, report, it: _it }, idx) => (
|
|
58
58
|
|
|
59
|
-
it(`test case #${idx}`, function() {
|
|
59
|
+
(_it || it)(`test case #${idx}`, function() {
|
|
60
60
|
|
|
61
61
|
if (!Array.isArray(report)) {
|
|
62
62
|
report = [
|
package/lib/traverse.js
CHANGED
|
@@ -3,32 +3,37 @@
|
|
|
3
3
|
* and call the passed visitor fn.
|
|
4
4
|
*
|
|
5
5
|
* @param {ModdleElement} element
|
|
6
|
-
* @param {Function}
|
|
6
|
+
* @param {{ enter?: Function; leave?: Function }} options
|
|
7
7
|
*/
|
|
8
|
-
module.exports = function traverse(element,
|
|
9
|
-
fn(element);
|
|
8
|
+
module.exports = function traverse(element, options) {
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
const enter = options.enter || null;
|
|
11
|
+
const leave = options.leave || null;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const enterSubTree = enter && enter(element);
|
|
14
|
+
|
|
15
|
+
const descriptor = element.$descriptor;
|
|
16
|
+
|
|
17
|
+
if (enterSubTree !== false && !descriptor.isGeneric) {
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const containedProperties = descriptor.properties.filter(p => {
|
|
20
|
+
return !p.isAttr && !p.isReference && p.type !== 'String';
|
|
21
|
+
});
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
containedProperties.forEach(p => {
|
|
24
|
+
if (p.name in element) {
|
|
25
|
+
const propertyValue = element[p.name];
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
if (p.isMany) {
|
|
28
|
+
propertyValue.forEach(child => {
|
|
29
|
+
traverse(child, options);
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
traverse(propertyValue, options);
|
|
33
|
+
}
|
|
31
34
|
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
leave && leave(element);
|
|
34
39
|
};
|
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": "7.0
|
|
4
|
+
"version": "7.3.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bpmnlint",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"bpmnlint-utils": "^1.0.2",
|
|
43
43
|
"cli-table": "^0.3.1",
|
|
44
44
|
"color-support": "^1.1.3",
|
|
45
|
+
"min-dash": "^3.5.2",
|
|
45
46
|
"mri": "^1.1.6",
|
|
46
47
|
"pluralize": "^7.0.0",
|
|
47
48
|
"tiny-glob": "^0.2.8"
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const {
|
|
2
|
+
is
|
|
3
|
+
} = require('bpmnlint-utils');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
flatten
|
|
7
|
+
} = require('min-dash');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A rule that checks that there is no BPMNDI information missing for elements,
|
|
11
|
+
* which require BPMNDI.
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function() {
|
|
14
|
+
|
|
15
|
+
function check(node, reporter) {
|
|
16
|
+
|
|
17
|
+
if (!is(node, 'bpmn:Definitions')) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// (1) Construct array of all BPMN elements
|
|
22
|
+
const bpmnElements = getAllBpmnElements(node.rootElements);
|
|
23
|
+
|
|
24
|
+
// (2) Filter BPMN elements without visual representation
|
|
25
|
+
const visualBpmnElements = bpmnElements.filter(hasVisualRepresentation);
|
|
26
|
+
|
|
27
|
+
// (3) Construct array of BPMNDI references
|
|
28
|
+
const diBpmnReferences = getAllDiBpmnReferences(node);
|
|
29
|
+
|
|
30
|
+
// (4) Report elements without BPMNDI
|
|
31
|
+
visualBpmnElements.forEach((element) => {
|
|
32
|
+
if (diBpmnReferences.indexOf(element.id) === -1) {
|
|
33
|
+
reporter.report(element.id, 'Element is missing bpmndi');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
check
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// helpers /////////////////////////////
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get all BPMN elements within a bpmn:Definitions node
|
|
49
|
+
*
|
|
50
|
+
* @param {array<ModdleElement>} rootElements - An array of Moddle rootElements
|
|
51
|
+
* @return {array<Object>} A flat array with all BPMN elements, each represented with { id: elementId, $type: elementType }
|
|
52
|
+
*
|
|
53
|
+
*/
|
|
54
|
+
function getAllBpmnElements(rootElements) {
|
|
55
|
+
return flatten(rootElements.map((rootElement) => {
|
|
56
|
+
|
|
57
|
+
const laneSet =
|
|
58
|
+
rootElement.laneSets && rootElement.laneSets[0] || rootElement.childLaneSet;
|
|
59
|
+
|
|
60
|
+
// Include
|
|
61
|
+
// * flowElements (e.g., tasks, sequenceFlows),
|
|
62
|
+
// * nested flowElements,
|
|
63
|
+
// * participants,
|
|
64
|
+
// * artifacts (groups),
|
|
65
|
+
// * laneSets
|
|
66
|
+
// * nested laneSets
|
|
67
|
+
// * childLaneSets
|
|
68
|
+
// * nested childLaneSets
|
|
69
|
+
const elements = flatten([].concat(
|
|
70
|
+
rootElement.flowElements || [],
|
|
71
|
+
(rootElement.flowElements && getAllBpmnElements(rootElement.flowElements.filter(hasFlowElements))) || [],
|
|
72
|
+
rootElement.participants || [],
|
|
73
|
+
rootElement.artifacts || [],
|
|
74
|
+
laneSet && laneSet.lanes || [],
|
|
75
|
+
laneSet && laneSet.lanes && getAllBpmnElements(laneSet.lanes.filter(hasChildLaneSet)) || []
|
|
76
|
+
));
|
|
77
|
+
|
|
78
|
+
if (elements.length > 0) {
|
|
79
|
+
return elements.map((element) => {
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
id: element.id,
|
|
83
|
+
$type: element.$type
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
} else {
|
|
87
|
+
|
|
88
|
+
// We are not interested in the rest here (DI)
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get all BPMN elements within a bpmn:Definitions node
|
|
96
|
+
*
|
|
97
|
+
* @param {ModdleElement} definitionsNode - A moddleElement representing the
|
|
98
|
+
* bpmn:Definitions element
|
|
99
|
+
* @return {array<String>} A flat array with all BPMNDI element ids part of
|
|
100
|
+
* this bpmn:Definitions node
|
|
101
|
+
*
|
|
102
|
+
*/
|
|
103
|
+
function getAllDiBpmnReferences(definitionsNode) {
|
|
104
|
+
return flatten(
|
|
105
|
+
definitionsNode.diagrams.map((diagram) => {
|
|
106
|
+
|
|
107
|
+
const diElements = diagram.plane.planeElement || [];
|
|
108
|
+
|
|
109
|
+
return diElements.map((element) => {
|
|
110
|
+
|
|
111
|
+
return element.bpmnElement.id;
|
|
112
|
+
});
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hasVisualRepresentation(element) {
|
|
118
|
+
const noVisRepresentation = ['bpmn:DataObject'];
|
|
119
|
+
|
|
120
|
+
return noVisRepresentation.includes(element.$type) ? false : true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hasFlowElements(element) {
|
|
124
|
+
return element.flowElements ? true : false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function hasChildLaneSet(element) {
|
|
128
|
+
return element.childLaneSet ? true : false;
|
|
129
|
+
}
|
package/rules/no-disconnected.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const {
|
|
2
|
-
isAny
|
|
2
|
+
isAny,
|
|
3
|
+
is
|
|
3
4
|
} = require('bpmnlint-utils');
|
|
4
5
|
|
|
5
6
|
|
|
@@ -21,6 +22,13 @@ module.exports = function() {
|
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// compensation activity and boundary events are
|
|
26
|
+
// linked visually via associations. If these associations
|
|
27
|
+
// exist we are fine, too
|
|
28
|
+
if (isCompensationLinked(node)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
const incoming = node.incoming || [];
|
|
25
33
|
const outgoing = node.outgoing || [];
|
|
26
34
|
|
|
@@ -32,5 +40,34 @@ module.exports = function() {
|
|
|
32
40
|
return {
|
|
33
41
|
check
|
|
34
42
|
};
|
|
35
|
-
|
|
36
43
|
};
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
// helpers /////////////////
|
|
47
|
+
|
|
48
|
+
function isCompensationBoundary(node) {
|
|
49
|
+
|
|
50
|
+
var eventDefinitions = node.eventDefinitions;
|
|
51
|
+
|
|
52
|
+
if (!is(node, 'bpmn:BoundaryEvent')) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!eventDefinitions || eventDefinitions.length !== 1) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return is(eventDefinitions[0], 'bpmn:CompensateEventDefinition');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isCompensationActivity(node) {
|
|
64
|
+
return node.isForCompensation;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isCompensationLinked(node) {
|
|
68
|
+
var source = isCompensationBoundary(node);
|
|
69
|
+
var target = isCompensationActivity(node);
|
|
70
|
+
|
|
71
|
+
// TODO(nikku): check, whether compensation association exists
|
|
72
|
+
return source || target;
|
|
73
|
+
}
|