bpmnlint 7.1.0 → 7.4.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 +18 -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/resolver/node-resolver.js +23 -2
- package/lib/support/compile-config.js +6 -4
- package/lib/testers/rule-tester.js +4 -4
- 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,24 @@ 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.4.0
|
|
10
|
+
|
|
11
|
+
* `FEAT`: resolve local package by name ([#64](https://github.com/bpmn-io/bpmnlint/pull/64))
|
|
12
|
+
|
|
13
|
+
## 7.3.0
|
|
14
|
+
|
|
15
|
+
* `FEAT`: support `--version` command line flag ([#65](https://github.com/bpmn-io/bpmnlint/pull/65))
|
|
16
|
+
* `FEAT`: allow rule testers to provide custom `it`
|
|
17
|
+
* `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))
|
|
18
|
+
|
|
19
|
+
## 7.2.1
|
|
20
|
+
|
|
21
|
+
* `FIX`: exclude compensation boundary and activity in `no-disconnected` check ([#60](https://github.com/bpmn-io/bpmnlint/pull/60))
|
|
22
|
+
|
|
23
|
+
## 7.2.0
|
|
24
|
+
|
|
25
|
+
* `FEAT`: add `no-bpmndi` rule ([#55](https://github.com/bpmn-io/bpmnlint/issues/55))
|
|
26
|
+
|
|
9
27
|
## 7.1.0
|
|
10
28
|
|
|
11
29
|
* `FEAT`: allow rules to skip sub-tree traversal ([#53](https://github.com/bpmn-io/bpmnlint/issues/53))
|
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
|
+
};
|
|
@@ -9,6 +9,12 @@ function NodeResolver(options) {
|
|
|
9
9
|
this.require = options && options.require || createScopedRequire(process.cwd());
|
|
10
10
|
|
|
11
11
|
this.requireLocal = options && options.requireLocal || require;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
this.pkg = this.require('./package.json').name;
|
|
15
|
+
} catch (err) {
|
|
16
|
+
this.pkg = '__unknown';
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
module.exports = NodeResolver;
|
|
@@ -16,6 +22,10 @@ module.exports = NodeResolver;
|
|
|
16
22
|
|
|
17
23
|
NodeResolver.prototype.resolveRule = function(pkg, ruleName) {
|
|
18
24
|
|
|
25
|
+
const originalPkg = pkg;
|
|
26
|
+
|
|
27
|
+
pkg = this.normalizePkg(pkg);
|
|
28
|
+
|
|
19
29
|
try {
|
|
20
30
|
if (pkg === 'bpmnlint') {
|
|
21
31
|
return this.requireLocal(`../../rules/${ruleName}`);
|
|
@@ -23,12 +33,16 @@ NodeResolver.prototype.resolveRule = function(pkg, ruleName) {
|
|
|
23
33
|
return this.require(`${pkg}/rules/${ruleName}`);
|
|
24
34
|
}
|
|
25
35
|
} catch (err) {
|
|
26
|
-
throw new Error('Cannot resolve rule <' + ruleName + '> from <' +
|
|
36
|
+
throw new Error('Cannot resolve rule <' + ruleName + '> from <' + originalPkg + '>');
|
|
27
37
|
}
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
NodeResolver.prototype.resolveConfig = function(pkg, configName) {
|
|
31
41
|
|
|
42
|
+
const originalPkg = pkg;
|
|
43
|
+
|
|
44
|
+
pkg = this.normalizePkg(pkg);
|
|
45
|
+
|
|
32
46
|
// resolve config via $PKG/config/$NAME
|
|
33
47
|
try {
|
|
34
48
|
if (pkg === 'bpmnlint') {
|
|
@@ -53,10 +67,17 @@ NodeResolver.prototype.resolveConfig = function(pkg, configName) {
|
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
throw new Error(
|
|
56
|
-
'Cannot resolve config <' + configName + '> from <' +
|
|
70
|
+
'Cannot resolve config <' + configName + '> from <' + originalPkg + '>'
|
|
57
71
|
);
|
|
58
72
|
};
|
|
59
73
|
|
|
74
|
+
NodeResolver.prototype.normalizePkg = function(pkg) {
|
|
75
|
+
if (pkg !== 'bpmnlint' && pkg === this.pkg) {
|
|
76
|
+
pkg = '.';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return pkg;
|
|
80
|
+
};
|
|
60
81
|
|
|
61
82
|
// helpers ////////////////////
|
|
62
83
|
|
|
@@ -7,13 +7,15 @@ const NodeResolver = require('../resolver/node-resolver');
|
|
|
7
7
|
* a { config, resolver } bundle, resolving all enabled rules.
|
|
8
8
|
*
|
|
9
9
|
* @param {Object} config the parsed bpmnlint configuration
|
|
10
|
-
*
|
|
10
|
+
* @param {NodeResolver} [resolver]
|
|
11
11
|
* @return {Promise<String>} the configuration compiled to a JS file
|
|
12
12
|
*/
|
|
13
|
-
async function compileConfig(config) {
|
|
13
|
+
async function compileConfig(config, resolver) {
|
|
14
|
+
|
|
15
|
+
resolver = resolver || new NodeResolver();
|
|
14
16
|
|
|
15
17
|
const linter = new Linter({
|
|
16
|
-
resolver
|
|
18
|
+
resolver
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
const resolvedRules = await linter.resolveConfiguredRules(config);
|
|
@@ -86,7 +88,7 @@ export default bundle;
|
|
|
86
88
|
} = linter.parseRuleName(key);
|
|
87
89
|
|
|
88
90
|
return `
|
|
89
|
-
import rule_${idx} from '${pkg}/rules/${ruleName}';
|
|
91
|
+
import rule_${idx} from '${resolver.normalizePkg(pkg)}/rules/${ruleName}';
|
|
90
92
|
cache['${pkg}/${ruleName}'] = rule_${idx};`;
|
|
91
93
|
}).join('\n');
|
|
92
94
|
|
|
@@ -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/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.
|
|
4
|
+
"version": "7.4.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
|
+
}
|