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 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
- [![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
+ };
@@ -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 <' + pkg + '>');
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 <' + pkg + '>'
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: new NodeResolver()
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.1.0",
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
+ }
@@ -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
+ }