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 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
- [![Build Status](https://travis-ci.com/bpmn-io/bpmnlint.svg?branch=master)](https://travis-ci.com/bpmn-io/bpmnlint)
3
+ [![CI](https://github.com/bpmn-io/bpmnlint/workflows/CI/badge.svg)](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
- ## Usage
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
+ };
@@ -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
- var cache = {};
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
- var resolver = new Resolver();
64
+ const resolver = new Resolver();
65
65
 
66
- var rules = ${serializedRules};
66
+ const rules = ${serializedRules};
67
67
 
68
- var config = {
68
+ const config = {
69
69
  rules: rules
70
70
  };
71
71
 
72
- var bundle = {
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
- traverse(moddleRoot, node => rule.check(node, reporter));
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} fn
6
+ * @param {{ enter?: Function; leave?: Function }} options
7
7
  */
8
- module.exports = function traverse(element, fn) {
9
- fn(element);
8
+ module.exports = function traverse(element, options) {
10
9
 
11
- var descriptor = element.$descriptor;
10
+ const enter = options.enter || null;
11
+ const leave = options.leave || null;
12
12
 
13
- if (descriptor.isGeneric) {
14
- return;
15
- }
13
+ const enterSubTree = enter && enter(element);
14
+
15
+ const descriptor = element.$descriptor;
16
+
17
+ if (enterSubTree !== false && !descriptor.isGeneric) {
16
18
 
17
- var containedProperties = descriptor.properties.filter(p => {
18
- return !p.isAttr && !p.isReference && p.type !== 'String';
19
- });
19
+ const containedProperties = descriptor.properties.filter(p => {
20
+ return !p.isAttr && !p.isReference && p.type !== 'String';
21
+ });
20
22
 
21
- containedProperties.forEach(p => {
22
- if (p.name in element) {
23
- const propertyValue = element[p.name];
23
+ containedProperties.forEach(p => {
24
+ if (p.name in element) {
25
+ const propertyValue = element[p.name];
24
26
 
25
- if (p.isMany) {
26
- propertyValue.forEach(child => {
27
- traverse(child, fn);
28
- });
29
- } else {
30
- traverse(propertyValue, fn);
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.1",
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
+ }
@@ -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
+ }