bpmnlint-plugin-camunda-compat 2.6.3 → 2.7.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/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',
@@ -33,8 +34,7 @@ const camundaCloud12Rules = withConfig(camundaCloud11Rules, { version: '1.2' });
33
34
  const camundaCloud13Rules = withConfig(camundaCloud12Rules, { version: '1.3' });
34
35
 
35
36
  const camundaCloud80Rules = withConfig({
36
- ...omit(camundaCloud13Rules, 'no-template'),
37
- 'secrets': 'error'
37
+ ...omit(camundaCloud13Rules, 'no-template')
38
38
  }, { version: '8.0' });
39
39
 
40
40
  const camundaCloud81Rules = withConfig({
@@ -93,6 +93,7 @@ const rules = {
93
93
  'message-reference': './rules/camunda-cloud/message-reference',
94
94
  'no-candidate-users': './rules/camunda-cloud/no-candidate-users',
95
95
  'no-expression': './rules/camunda-cloud/no-expression',
96
+ 'no-loop': './rules/camunda-cloud/no-loop',
96
97
  'no-multiple-none-start-events': './rules/camunda-cloud/no-multiple-none-start-events',
97
98
  'no-propagate-all-parent-variables': './rules/camunda-cloud/no-propagate-all-parent-variables',
98
99
  'no-signal-event-sub-process': './rules/camunda-cloud/no-signal-event-sub-process',
@@ -153,10 +154,11 @@ module.exports = {
153
154
  rules
154
155
  };
155
156
 
156
- function withConfig(rules, config, type = 'error') {
157
+ function withConfig(rules, config) {
157
158
  let rulesWithConfig = {};
158
159
 
159
160
  for (let name in rules) {
161
+ const type = Array.isArray(rules[ name ]) ? rules[ name ][0] : rules[ name ];
160
162
  rulesWithConfig[ name ] = [ type, config ];
161
163
  }
162
164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmnlint-plugin-camunda-compat",
3
- "version": "2.6.3",
3
+ "version": "2.7.1",
4
4
  "description": "A bpmnlint plug-in for Camunda Platform compatibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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, 'bpmn:LinkEventDefinition');
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
- ]) && getEventDefinition(element, 'bpmn:LinkEventDefinition');
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
- && getEventDefinition(element, 'bpmn:LinkEventDefinition');
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
+ }
@@ -98,12 +98,12 @@ function getReport(propertyName, node, parentNode) {
98
98
  const path = getPath(node, parentNode);
99
99
 
100
100
  return {
101
- message: `Property <${ propertyName }> is not a valid secret`,
101
+ message: `Property <${ propertyName }> uses deprecated secret expression format`,
102
102
  path: path
103
103
  ? [ ...getPath(node, parentNode), propertyName ]
104
104
  : [ propertyName ],
105
105
  data: {
106
- type: ERROR_TYPES.SECRET_EXPRESSION_INVALID,
106
+ type: ERROR_TYPES.SECRET_EXPRESSION_FORMAT_DEPRECATED,
107
107
  node,
108
108
  parentNode: parentNode,
109
109
  property: propertyName
@@ -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
  }
@@ -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',
@@ -19,5 +20,5 @@ module.exports.ERROR_TYPES = Object.freeze({
19
20
  PROPERTY_VALUE_DUPLICATED: 'camunda.propertyValueDuplicated',
20
21
  PROPERTY_VALUE_NOT_ALLOWED: 'camunda.propertyValueNotAllowed',
21
22
  PROPERTY_VALUE_REQUIRED: 'camunda.propertyValueRequired',
22
- SECRET_EXPRESSION_INVALID: 'camunda.secretExpressionInvalid'
23
+ SECRET_EXPRESSION_FORMAT_DEPRECATED: 'camunda.secretExpressionFormatDeprecated'
23
24
  });