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 CHANGED
@@ -3,6 +3,7 @@ const allRules = [
3
3
  'end-event-required',
4
4
  'event-sub-process-typed-start-event',
5
5
  'fake-join',
6
+ 'global',
6
7
  'label-required',
7
8
  'link-event',
8
9
  'no-bpmndi',
@@ -4,6 +4,7 @@ module.exports = {
4
4
  'end-event-required': 'error',
5
5
  'event-sub-process-typed-start-event': 'error',
6
6
  'fake-join': 'warn',
7
+ 'global': 'warn',
7
8
  'label-required': 'error',
8
9
  'link-event': 'error',
9
10
  'no-bpmndi': 'error',
@@ -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?: Function; leave?: Function }} options
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
@@ -0,0 +1,5 @@
1
+ export type ModdleElement = any;
2
+
3
+ export type EnterFn = (element: ModdleElement) => boolean | null;
4
+
5
+ export type LeaveFn = (element: ModdleElement) => void;
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.2.3",
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.1",
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.1.1",
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.3.8",
56
- "eslint": "^8.49.0",
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.2.0",
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"
@@ -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;
@@ -23,10 +23,6 @@ module.exports = function() {
23
23
  return;
24
24
  }
25
25
 
26
- if (is(node, 'bpmn:BoundaryEvent')) {
27
- return;
28
- }
29
-
30
26
  // ignore sub-processes
31
27
  if (is(node, 'bpmn:SubProcess')) {
32
28
 
@@ -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
+ };