bpmnlint-plugin-camunda-compat 2.6.2 → 2.7.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/index.js +2 -0
- package/package.json +1 -1
- package/rules/camunda-cloud/link-event.js +7 -3
- package/rules/camunda-cloud/no-loop.js +146 -0
- package/rules/camunda-cloud/secrets.js +5 -1
- package/rules/camunda-cloud/user-task-form.js +1 -18
- package/rules/utils/element.js +21 -1
- package/rules/utils/error-types.js +1 -0
package/index.js
CHANGED
@@ -13,6 +13,7 @@ const camundaCloud10Rules = withConfig({
|
|
13
13
|
'message-reference': 'error',
|
14
14
|
'no-candidate-users': 'error',
|
15
15
|
'no-expression': 'error',
|
16
|
+
'no-loop': 'error',
|
16
17
|
'no-multiple-none-start-events': 'error',
|
17
18
|
'no-propagate-all-parent-variables': 'error',
|
18
19
|
'no-task-schedule': 'error',
|
@@ -93,6 +94,7 @@ const rules = {
|
|
93
94
|
'message-reference': './rules/camunda-cloud/message-reference',
|
94
95
|
'no-candidate-users': './rules/camunda-cloud/no-candidate-users',
|
95
96
|
'no-expression': './rules/camunda-cloud/no-expression',
|
97
|
+
'no-loop': './rules/camunda-cloud/no-loop',
|
96
98
|
'no-multiple-none-start-events': './rules/camunda-cloud/no-multiple-none-start-events',
|
97
99
|
'no-propagate-all-parent-variables': './rules/camunda-cloud/no-propagate-all-parent-variables',
|
98
100
|
'no-signal-event-sub-process': './rules/camunda-cloud/no-signal-event-sub-process',
|
package/package.json
CHANGED
@@ -89,7 +89,7 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
89
89
|
|
90
90
|
// check for missing link catch & throw event names
|
91
91
|
if (isLinkEvent(node)) {
|
92
|
-
const linkEventDefinition = getEventDefinition(node
|
92
|
+
const linkEventDefinition = getEventDefinition(node);
|
93
93
|
|
94
94
|
const errors = hasProperties(linkEventDefinition, {
|
95
95
|
name: {
|
@@ -109,15 +109,19 @@ module.exports = skipInNonExecutableProcess(function() {
|
|
109
109
|
});
|
110
110
|
|
111
111
|
function isLinkEvent(element) {
|
112
|
+
const eventDefinition = getEventDefinition(element);
|
113
|
+
|
112
114
|
return isAny(element, [
|
113
115
|
'bpmn:IntermediateCatchEvent',
|
114
116
|
'bpmn:IntermediateThrowEvent'
|
115
|
-
]) &&
|
117
|
+
]) && eventDefinition && is(eventDefinition, 'bpmn:LinkEventDefinition');
|
116
118
|
}
|
117
119
|
|
118
120
|
function isLinkCatchEvent(element) {
|
121
|
+
const eventDefinition = getEventDefinition(element);
|
122
|
+
|
119
123
|
return is(element, 'bpmn:IntermediateCatchEvent')
|
120
|
-
&&
|
124
|
+
&& eventDefinition && is(eventDefinition, 'bpmn:LinkEventDefinition');
|
121
125
|
}
|
122
126
|
|
123
127
|
function getLinkCatchEvents(flowElementsContainer) {
|
@@ -0,0 +1,146 @@
|
|
1
|
+
const { isString } = require('min-dash');
|
2
|
+
|
3
|
+
const { is } = require('bpmnlint-utils');
|
4
|
+
|
5
|
+
const {
|
6
|
+
findExtensionElement,
|
7
|
+
findParent,
|
8
|
+
isAnyExactly
|
9
|
+
} = require('../utils/element');
|
10
|
+
|
11
|
+
const { reportErrors } = require('../utils/reporter');
|
12
|
+
|
13
|
+
const { ERROR_TYPES } = require('../utils/error-types');
|
14
|
+
|
15
|
+
const { skipInNonExecutableProcess } = require('../utils/rule');
|
16
|
+
|
17
|
+
const LOOP_REQUIRED_ELEMENT_TYPES = [
|
18
|
+
'bpmn:CallActivity',
|
19
|
+
'bpmn:ManualTask',
|
20
|
+
'bpmn:Task'
|
21
|
+
];
|
22
|
+
|
23
|
+
const LOOP_ELEMENT_TYPES = [
|
24
|
+
...LOOP_REQUIRED_ELEMENT_TYPES,
|
25
|
+
'bpmn:StartEvent',
|
26
|
+
'bpmn:EndEvent',
|
27
|
+
'bpmn:ManualTask',
|
28
|
+
'bpmn:ExclusiveGateway',
|
29
|
+
'bpmn:InclusiveGateway',
|
30
|
+
'bpmn:ParallelGateway',
|
31
|
+
'bpmn:SubProcess',
|
32
|
+
'bpmn:Task'
|
33
|
+
];
|
34
|
+
|
35
|
+
module.exports = skipInNonExecutableProcess(function() {
|
36
|
+
function check(node, reporter) {
|
37
|
+
if (!is(node, 'bpmn:Process')) {
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
|
41
|
+
const error = getFlowElements(node)
|
42
|
+
.filter(flowElement => {
|
43
|
+
return isAnyExactly(flowElement, LOOP_ELEMENT_TYPES);
|
44
|
+
})
|
45
|
+
.reduce((error, flowElement) => {
|
46
|
+
return error || findLoop(flowElement, node);
|
47
|
+
}, null);
|
48
|
+
|
49
|
+
if (error) {
|
50
|
+
reportErrors(node, reporter, error);
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
return {
|
55
|
+
check
|
56
|
+
};
|
57
|
+
});
|
58
|
+
|
59
|
+
function findLoop(flowElement, parentElement, visitedFlowElements = []) {
|
60
|
+
|
61
|
+
// (1.1) is not a loop
|
62
|
+
if (!isAnyExactly(flowElement, LOOP_ELEMENT_TYPES)) {
|
63
|
+
return null;
|
64
|
+
}
|
65
|
+
|
66
|
+
const nextFlowElements = getNextFlowElements(flowElement);
|
67
|
+
|
68
|
+
// (1.2) is not a loop
|
69
|
+
if (!nextFlowElements.length) {
|
70
|
+
return null;
|
71
|
+
}
|
72
|
+
|
73
|
+
// (2) may be a loop
|
74
|
+
if (!visitedFlowElements.includes(flowElement)) {
|
75
|
+
return nextFlowElements.reduce((error, nextFlowElement) => {
|
76
|
+
return error || findLoop(nextFlowElement, parentElement, [ ...visitedFlowElements, flowElement ]);
|
77
|
+
}, null);
|
78
|
+
}
|
79
|
+
|
80
|
+
// (3) is a loop but ignored
|
81
|
+
if (isIgnoredLoop(visitedFlowElements)) {
|
82
|
+
return null;
|
83
|
+
}
|
84
|
+
|
85
|
+
const elements = visitedFlowElements.slice(visitedFlowElements.indexOf(flowElement));
|
86
|
+
|
87
|
+
// (4) is a loop
|
88
|
+
return {
|
89
|
+
message: `Loop detected: ${ elements.map(({ id }) => id).join(' -> ') } -> ${ flowElement.id }`,
|
90
|
+
path: null,
|
91
|
+
data: {
|
92
|
+
type: ERROR_TYPES.LOOP_NOT_ALLOWED,
|
93
|
+
node: parentElement,
|
94
|
+
parentNode: null,
|
95
|
+
elements: elements.map(({ id }) => id)
|
96
|
+
}
|
97
|
+
};
|
98
|
+
}
|
99
|
+
|
100
|
+
function getFlowElements(node) {
|
101
|
+
return node.get('flowElements').reduce((flowElements, flowElement) => {
|
102
|
+
if (is(flowElement, 'bpmn:FlowElementsContainer')) {
|
103
|
+
return [ ...flowElements, ...getFlowElements(flowElement) ];
|
104
|
+
}
|
105
|
+
|
106
|
+
return [ ...flowElements, flowElement ];
|
107
|
+
}, []);
|
108
|
+
}
|
109
|
+
|
110
|
+
function getNextFlowElements(flowElement) {
|
111
|
+
if (is(flowElement, 'bpmn:CallActivity')) {
|
112
|
+
const calledElement = findExtensionElement(flowElement, 'zeebe:CalledElement');
|
113
|
+
|
114
|
+
if (calledElement) {
|
115
|
+
const processId = calledElement.get('processId');
|
116
|
+
|
117
|
+
if (isString(processId) && !isFeel(processId)) {
|
118
|
+
const process = findParent(flowElement, 'bpmn:Process');
|
119
|
+
|
120
|
+
if (process && process.get('id') === processId) {
|
121
|
+
return process.get('flowElements').filter(flowElement => is(flowElement, 'bpmn:StartEvent'));
|
122
|
+
}
|
123
|
+
}
|
124
|
+
}
|
125
|
+
} else if (is(flowElement, 'bpmn:SubProcess')) {
|
126
|
+
return flowElement
|
127
|
+
.get('flowElements').filter(flowElement => is(flowElement, 'bpmn:StartEvent'));
|
128
|
+
} else if (is(flowElement, 'bpmn:EndEvent')) {
|
129
|
+
const parent = flowElement.$parent;
|
130
|
+
|
131
|
+
if (is(parent, 'bpmn:SubProcess')) {
|
132
|
+
flowElement = parent;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
return flowElement
|
137
|
+
.get('outgoing').filter(outgoing => is(outgoing, 'bpmn:SequenceFlow')).map(sequenceFlow => sequenceFlow.get('targetRef'));
|
138
|
+
}
|
139
|
+
|
140
|
+
function isIgnoredLoop(elements) {
|
141
|
+
return !elements.some(element => isAnyExactly(element, LOOP_REQUIRED_ELEMENT_TYPES));
|
142
|
+
}
|
143
|
+
|
144
|
+
function isFeel(value) {
|
145
|
+
return isString(value) && value.startsWith('=');
|
146
|
+
}
|
@@ -95,9 +95,13 @@ function validateSubscription(node) {
|
|
95
95
|
}
|
96
96
|
|
97
97
|
function getReport(propertyName, node, parentNode) {
|
98
|
+
const path = getPath(node, parentNode);
|
99
|
+
|
98
100
|
return {
|
99
101
|
message: `Property <${ propertyName }> is not a valid secret`,
|
100
|
-
path:
|
102
|
+
path: path
|
103
|
+
? [ ...getPath(node, parentNode), propertyName ]
|
104
|
+
: [ propertyName ],
|
101
105
|
data: {
|
102
106
|
type: ERROR_TYPES.SECRET_EXPRESSION_INVALID,
|
103
107
|
node,
|
@@ -3,6 +3,7 @@ const { is } = require('bpmnlint-utils');
|
|
3
3
|
const {
|
4
4
|
findExtensionElement,
|
5
5
|
findExtensionElements,
|
6
|
+
findParent,
|
6
7
|
hasProperties
|
7
8
|
} = require('../utils/element');
|
8
9
|
|
@@ -76,22 +77,4 @@ function findUserTaskForm(node, formKey) {
|
|
76
77
|
return `camunda-forms:bpmn:${ id }` === formKey;
|
77
78
|
});
|
78
79
|
}
|
79
|
-
}
|
80
|
-
|
81
|
-
function findParent(node, type) {
|
82
|
-
if (!node) {
|
83
|
-
return null;
|
84
|
-
}
|
85
|
-
|
86
|
-
const parent = node.$parent;
|
87
|
-
|
88
|
-
if (!parent) {
|
89
|
-
return node;
|
90
|
-
}
|
91
|
-
|
92
|
-
if (is(parent, type)) {
|
93
|
-
return parent;
|
94
|
-
}
|
95
|
-
|
96
|
-
return findParent(parent, type);
|
97
80
|
}
|
package/rules/utils/element.js
CHANGED
@@ -450,4 +450,24 @@ function addAllowedVersion(data, allowedVersion) {
|
|
450
450
|
...data,
|
451
451
|
allowedVersion
|
452
452
|
};
|
453
|
-
}
|
453
|
+
}
|
454
|
+
|
455
|
+
function findParent(node, type) {
|
456
|
+
if (!node) {
|
457
|
+
return null;
|
458
|
+
}
|
459
|
+
|
460
|
+
const parent = node.$parent;
|
461
|
+
|
462
|
+
if (!parent) {
|
463
|
+
return node;
|
464
|
+
}
|
465
|
+
|
466
|
+
if (is(parent, type)) {
|
467
|
+
return parent;
|
468
|
+
}
|
469
|
+
|
470
|
+
return findParent(parent, type);
|
471
|
+
}
|
472
|
+
|
473
|
+
module.exports.findParent = findParent;
|
@@ -11,6 +11,7 @@ module.exports.ERROR_TYPES = Object.freeze({
|
|
11
11
|
EXTENSION_ELEMENT_NOT_ALLOWED: 'camunda.extensionElementNotAllowed',
|
12
12
|
EXTENSION_ELEMENT_REQUIRED: 'camunda.extensionElementRequired',
|
13
13
|
FEEL_EXPRESSION_INVALID: 'camunda.feelExpressionInvalid',
|
14
|
+
LOOP_NOT_ALLOWED: 'camunda.loopNotAllowed',
|
14
15
|
ATTACHED_TO_REF_ELEMENT_TYPE_NOT_ALLOWED: 'camunda.attachedToRefElementTypeNotAllowed',
|
15
16
|
PROPERTY_DEPENDENT_REQUIRED: 'camunda.propertyDependentRequired',
|
16
17
|
PROPERTY_NOT_ALLOWED: 'camunda.propertyNotAllowed',
|