Skip to content

Commit

Permalink
feat: add ad-hoc-sub-process rule
Browse files Browse the repository at this point in the history
Verify that an ad-hoc sub-process is valid:

* Must not contain start or end events.
* Every intermediate event must have an outgoing sequence flow.

Related to camunda/camunda-modeler#4811
  • Loading branch information
jarekdanielak authored and nikku committed Feb 17, 2025
1 parent 28ab4cc commit f5146c2
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 56 deletions.
1 change: 1 addition & 0 deletions config/all.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const allRules = [
'ad-hoc-sub-process',
'conditional-flows',
'end-event-required',
'event-sub-process-typed-start-event',
Expand Down
1 change: 1 addition & 0 deletions config/recommended.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
rules: {
'ad-hoc-sub-process': 'error',
'conditional-flows': 'error',
'end-event-required': 'error',
'event-sub-process-typed-start-event': 'error',
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

This library implements the following lint rules:

* [Ad-Hoc Sub-Process](./ad-hoc-sub-process.md)
* [Conditional Flows](./conditional-flows.md)
* [End Event Required](./end-event-required.md)
* [Event Sub-Process Typed Start Event](./event-sub-process-typed-start-event.md)
Expand Down
20 changes: 20 additions & 0 deletions docs/rules/ad-hoc-sub-process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Ad-hoc Sub-Process (ad-hoc-sub-process)

Ensure that an Ad-Hoc Sub-Process is valid according to the BPMN specification:

- Must not contain start or end events.
- Every intermediate event must have an outgoing sequence flow.


Example of __incorrect__ usage for this rule:

![Incorrect usage example](./examples/ad-hoc-sub-process-incorrect.png)

Cf. [`ad-hoc-sub-process-incorrect.bpmn`](./examples/no-complex-gateway-incorrect.bpmn).


Example of __correct__ usage for this rule:

![Correct usage example](./examples/ad-hoc-sub-process-correct.png)

Cf. [`ad-hoc-sub-process-correct.bpmn`](./examples/no-complex-gateway-correct.bpmn).
58 changes: 58 additions & 0 deletions docs/rules/examples/ad-hoc-sub-process-correct.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="Camunda Modeler" exporterVersion="5.32.0-rc.0">
<process id="Process_1" isExecutable="false">
<adHocSubProcess id="Activity_1yjcy2x">
<task id="Activity_0o1bgqx">
<outgoing>Flow_0njsews</outgoing>
</task>
<task id="Activity_142jk6n">
<incoming>Flow_0njsews</incoming>
</task>
<sequenceFlow id="Flow_0njsews" sourceRef="Activity_0o1bgqx" targetRef="Activity_142jk6n" />
<intermediateCatchEvent id="Event_0q40via">
<outgoing>Flow_1ykoger</outgoing>
<conditionalEventDefinition id="ConditionalEventDefinition_0q04q9v">
<condition xsi:type="tFormalExpression" />
</conditionalEventDefinition>
</intermediateCatchEvent>
<task id="Activity_12w8ibt" />
<task id="Activity_1u082wx">
<incoming>Flow_1ykoger</incoming>
</task>
<sequenceFlow id="Flow_1ykoger" sourceRef="Event_0q40via" targetRef="Activity_1u082wx" />
</adHocSubProcess>
</process>
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Activity_02xy6cc_di" bpmnElement="Activity_1yjcy2x" isExpanded="true">
<omgdc:Bounds x="160" y="80" width="390" height="410" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_12w8ibt_di" bpmnElement="Activity_12w8ibt">
<omgdc:Bounds x="220" y="120" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0o1bgqx_di" bpmnElement="Activity_0o1bgqx">
<omgdc:Bounds x="220" y="240" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_1b353o5" bpmnElement="Activity_142jk6n">
<omgdc:Bounds x="380" y="240" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1u082wx_di" bpmnElement="Activity_1u082wx">
<omgdc:Bounds x="380" y="360" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_069bzsw_di" bpmnElement="Event_0q40via">
<omgdc:Bounds x="252" y="382" width="36" height="36" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="408" y="485" width="84" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0njsews_di" bpmnElement="Flow_0njsews">
<omgdi:waypoint x="320" y="280" />
<omgdi:waypoint x="380" y="280" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ykoger_di" bpmnElement="Flow_1ykoger">
<omgdi:waypoint x="288" y="400" />
<omgdi:waypoint x="380" y="400" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions docs/rules/examples/ad-hoc-sub-process-incorrect.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="Camunda Modeler" exporterVersion="5.32.0-rc.0">
<process id="Process_1" isExecutable="false">
<adHocSubProcess id="Activity_1yjcy2x">
<intermediateCatchEvent id="Event_0q40via">
<conditionalEventDefinition id="ConditionalEventDefinition_0q04q9v">
<condition xsi:type="tFormalExpression" />
</conditionalEventDefinition>
</intermediateCatchEvent>
<startEvent id="Event_03k6pnd">
<outgoing>Flow_0ej0cl8</outgoing>
</startEvent>
<endEvent id="Event_03gcp25">
<incoming>Flow_0ej0cl8</incoming>
</endEvent>
<sequenceFlow id="Flow_0ej0cl8" sourceRef="Event_03k6pnd" targetRef="Event_03gcp25" />
</adHocSubProcess>
</process>
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="Activity_02xy6cc_di" bpmnElement="Activity_1yjcy2x" isExpanded="true">
<omgdc:Bounds x="160" y="80" width="390" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_03k6pnd_di" bpmnElement="Event_03k6pnd">
<omgdc:Bounds x="252" y="132" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_03gcp25_di" bpmnElement="Event_03gcp25">
<omgdc:Bounds x="422" y="132" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_069bzsw_di" bpmnElement="Event_0q40via">
<omgdc:Bounds x="252" y="232" width="36" height="36" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="408" y="485" width="84" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0ej0cl8_di" bpmnElement="Flow_0ej0cl8">
<omgdi:waypoint x="288" y="150" />
<omgdi:waypoint x="422" y="150" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions rules/ad-hoc-sub-process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const {
is,
isAny
} = require('bpmnlint-utils');

/**
* A rule that ensures that an Ad Hoc Sub Process is valid according to the BPMN spec:
*
* - No start or end events
* - Every intermediate event has an outgoing sequence flow
*/
module.exports = function() {

function check(node, reporter) {

if (!is(node, 'bpmn:AdHocSubProcess')) {
return;
}

const flowElements = node.flowElements || [];

flowElements.forEach(function(flowElement) {

if (is(flowElement, 'bpmn:StartEvent')) {
reporter.report(flowElement.id, 'A <Start Event> is not allowed in <Ad Hoc Sub Process>');
}

if (is(flowElement, 'bpmn:EndEvent')) {
reporter.report(flowElement.id, 'An <End Event> is not allowed in <Ad Hoc Sub Process>');
}

if (isAny(flowElement, [ 'bpmn:IntermediateCatchEvent', 'bpmn:IntermediateThrowEvent' ])) {
if (!flowElement.outgoing || flowElement.outgoing.length === 0) {
reporter.report(flowElement.id, 'An intermediate event inside <Ad Hoc Sub Process> must have an outgoing sequence flow');
}
}

});
}

return {
check
};

};
117 changes: 61 additions & 56 deletions test/integration/compilation/test/bpmnlintrc.expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Resolver.prototype.resolveConfig = function(pkg, configName) {
const resolver = new Resolver();

const rules = {
"ad-hoc-sub-process": "error",
"conditional-flows": "error",
"end-event-required": "error",
"event-sub-process-typed-start-event": "error",
Expand Down Expand Up @@ -72,114 +73,118 @@ export { resolver, config };

export default bundle;

import rule_0 from 'bpmnlint/rules/conditional-flows';
import rule_0 from 'bpmnlint/rules/ad-hoc-sub-process';

cache['bpmnlint/conditional-flows'] = rule_0;
cache['bpmnlint/ad-hoc-sub-process'] = rule_0;

import rule_1 from 'bpmnlint/rules/end-event-required';
import rule_1 from 'bpmnlint/rules/conditional-flows';

cache['bpmnlint/end-event-required'] = rule_1;
cache['bpmnlint/conditional-flows'] = rule_1;

import rule_2 from 'bpmnlint/rules/event-sub-process-typed-start-event';
import rule_2 from 'bpmnlint/rules/end-event-required';

cache['bpmnlint/event-sub-process-typed-start-event'] = rule_2;
cache['bpmnlint/end-event-required'] = rule_2;

import rule_3 from 'bpmnlint/rules/fake-join';
import rule_3 from 'bpmnlint/rules/event-sub-process-typed-start-event';

cache['bpmnlint/fake-join'] = rule_3;
cache['bpmnlint/event-sub-process-typed-start-event'] = rule_3;

import rule_4 from 'bpmnlint/rules/global';
import rule_4 from 'bpmnlint/rules/fake-join';

cache['bpmnlint/global'] = rule_4;
cache['bpmnlint/fake-join'] = rule_4;

import rule_5 from 'bpmnlint/rules/label-required';
import rule_5 from 'bpmnlint/rules/global';

cache['bpmnlint/label-required'] = rule_5;
cache['bpmnlint/global'] = rule_5;

import rule_6 from 'bpmnlint/rules/link-event';
import rule_6 from 'bpmnlint/rules/label-required';

cache['bpmnlint/link-event'] = rule_6;
cache['bpmnlint/label-required'] = rule_6;

import rule_7 from 'bpmnlint/rules/no-bpmndi';
import rule_7 from 'bpmnlint/rules/link-event';

cache['bpmnlint/no-bpmndi'] = rule_7;
cache['bpmnlint/link-event'] = rule_7;

import rule_8 from 'bpmnlint/rules/no-complex-gateway';
import rule_8 from 'bpmnlint/rules/no-bpmndi';

cache['bpmnlint/no-complex-gateway'] = rule_8;
cache['bpmnlint/no-bpmndi'] = rule_8;

import rule_9 from 'bpmnlint/rules/no-disconnected';
import rule_9 from 'bpmnlint/rules/no-complex-gateway';

cache['bpmnlint/no-disconnected'] = rule_9;
cache['bpmnlint/no-complex-gateway'] = rule_9;

import rule_10 from 'bpmnlint/rules/no-duplicate-sequence-flows';
import rule_10 from 'bpmnlint/rules/no-disconnected';

cache['bpmnlint/no-duplicate-sequence-flows'] = rule_10;
cache['bpmnlint/no-disconnected'] = rule_10;

import rule_11 from 'bpmnlint/rules/no-gateway-join-fork';
import rule_11 from 'bpmnlint/rules/no-duplicate-sequence-flows';

cache['bpmnlint/no-gateway-join-fork'] = rule_11;
cache['bpmnlint/no-duplicate-sequence-flows'] = rule_11;

import rule_12 from 'bpmnlint/rules/no-implicit-split';
import rule_12 from 'bpmnlint/rules/no-gateway-join-fork';

cache['bpmnlint/no-implicit-split'] = rule_12;
cache['bpmnlint/no-gateway-join-fork'] = rule_12;

import rule_13 from 'bpmnlint/rules/no-implicit-end';
import rule_13 from 'bpmnlint/rules/no-implicit-split';

cache['bpmnlint/no-implicit-end'] = rule_13;
cache['bpmnlint/no-implicit-split'] = rule_13;

import rule_14 from 'bpmnlint/rules/no-implicit-start';
import rule_14 from 'bpmnlint/rules/no-implicit-end';

cache['bpmnlint/no-implicit-start'] = rule_14;
cache['bpmnlint/no-implicit-end'] = rule_14;

import rule_15 from 'bpmnlint/rules/no-inclusive-gateway';
import rule_15 from 'bpmnlint/rules/no-implicit-start';

cache['bpmnlint/no-inclusive-gateway'] = rule_15;
cache['bpmnlint/no-implicit-start'] = rule_15;

import rule_16 from 'bpmnlint/rules/no-overlapping-elements';
import rule_16 from 'bpmnlint/rules/no-inclusive-gateway';

cache['bpmnlint/no-overlapping-elements'] = rule_16;
cache['bpmnlint/no-inclusive-gateway'] = rule_16;

import rule_17 from 'bpmnlint/rules/single-blank-start-event';
import rule_17 from 'bpmnlint/rules/no-overlapping-elements';

cache['bpmnlint/single-blank-start-event'] = rule_17;
cache['bpmnlint/no-overlapping-elements'] = rule_17;

import rule_18 from 'bpmnlint/rules/single-event-definition';
import rule_18 from 'bpmnlint/rules/single-blank-start-event';

cache['bpmnlint/single-event-definition'] = rule_18;
cache['bpmnlint/single-blank-start-event'] = rule_18;

import rule_19 from 'bpmnlint/rules/start-event-required';
import rule_19 from 'bpmnlint/rules/single-event-definition';

cache['bpmnlint/start-event-required'] = rule_19;
cache['bpmnlint/single-event-definition'] = rule_19;

import rule_20 from 'bpmnlint/rules/sub-process-blank-start-event';
import rule_20 from 'bpmnlint/rules/start-event-required';

cache['bpmnlint/sub-process-blank-start-event'] = rule_20;
cache['bpmnlint/start-event-required'] = rule_20;

import rule_21 from 'bpmnlint/rules/superfluous-gateway';
import rule_21 from 'bpmnlint/rules/sub-process-blank-start-event';

cache['bpmnlint/superfluous-gateway'] = rule_21;
cache['bpmnlint/sub-process-blank-start-event'] = rule_21;

import rule_22 from 'bpmnlint/rules/superfluous-termination';
import rule_22 from 'bpmnlint/rules/superfluous-gateway';

cache['bpmnlint/superfluous-termination'] = rule_22;
cache['bpmnlint/superfluous-gateway'] = rule_22;

import rule_23 from 'bpmnlint-plugin-test/rules/no-label-foo';
import rule_23 from 'bpmnlint/rules/superfluous-termination';

cache['bpmnlint-plugin-test/no-label-foo'] = rule_23;
cache['bpmnlint/superfluous-termination'] = rule_23;

import rule_24 from 'bpmnlint-plugin-exported/src/foo';
import rule_24 from 'bpmnlint-plugin-test/rules/no-label-foo';

cache['bpmnlint-plugin-exported/foo'] = rule_24;
cache['bpmnlint-plugin-test/no-label-foo'] = rule_24;

import rule_25 from 'bpmnlint-plugin-exported/src/bar';
import rule_25 from 'bpmnlint-plugin-exported/src/foo';

cache['bpmnlint-plugin-exported/bar'] = rule_25;
cache['bpmnlint-plugin-exported/foo'] = rule_25;

import rule_26 from 'bpmnlint-plugin-exported/rules/baz';
import rule_26 from 'bpmnlint-plugin-exported/src/bar';

cache['bpmnlint-plugin-exported/baz'] = rule_26;
cache['bpmnlint-plugin-exported/bar'] = rule_26;

import rule_27 from 'bpmnlint-plugin-exported/src/foo';
import rule_27 from 'bpmnlint-plugin-exported/rules/baz';

cache['bpmnlint-plugin-exported/foo-absolute'] = rule_27;
cache['bpmnlint-plugin-exported/baz'] = rule_27;

import rule_28 from 'bpmnlint-plugin-exported/src/foo';

cache['bpmnlint-plugin-exported/foo-absolute'] = rule_28;
Loading

0 comments on commit f5146c2

Please sign in to comment.