diff --git a/config/all.js b/config/all.js index 1a54780..e18d76c 100644 --- a/config/all.js +++ b/config/all.js @@ -1,4 +1,5 @@ const allRules = [ + 'ad-hoc-sub-process', 'conditional-flows', 'end-event-required', 'event-sub-process-typed-start-event', diff --git a/config/recommended.js b/config/recommended.js index d9fbe9a..d8cc9a1 100644 --- a/config/recommended.js +++ b/config/recommended.js @@ -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', diff --git a/docs/rules/README.md b/docs/rules/README.md index 342ada4..30537a1 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -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) diff --git a/docs/rules/ad-hoc-sub-process.md b/docs/rules/ad-hoc-sub-process.md new file mode 100644 index 0000000..1ce397f --- /dev/null +++ b/docs/rules/ad-hoc-sub-process.md @@ -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). diff --git a/docs/rules/examples/ad-hoc-sub-process-correct.bpmn b/docs/rules/examples/ad-hoc-sub-process-correct.bpmn new file mode 100644 index 0000000..240e40e --- /dev/null +++ b/docs/rules/examples/ad-hoc-sub-process-correct.bpmn @@ -0,0 +1,62 @@ + + + + + + Flow_0ki1200 + + + + Flow_0togudw + + + + Flow_0togudw + + + + + Flow_0ki1200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/rules/examples/ad-hoc-sub-process-correct.png b/docs/rules/examples/ad-hoc-sub-process-correct.png new file mode 100644 index 0000000..0d88823 Binary files /dev/null and b/docs/rules/examples/ad-hoc-sub-process-correct.png differ diff --git a/docs/rules/examples/ad-hoc-sub-process-incorrect.bpmn b/docs/rules/examples/ad-hoc-sub-process-incorrect.bpmn new file mode 100644 index 0000000..b54b9bb --- /dev/null +++ b/docs/rules/examples/ad-hoc-sub-process-incorrect.bpmn @@ -0,0 +1,70 @@ + + + + + + + + + + Flow_0w1fi7u + Flow_10hamq6 + + + Flow_0w1fi7u + + + + Flow_10hamq6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/rules/examples/ad-hoc-sub-process-incorrect.png b/docs/rules/examples/ad-hoc-sub-process-incorrect.png new file mode 100644 index 0000000..f9bc94e Binary files /dev/null and b/docs/rules/examples/ad-hoc-sub-process-incorrect.png differ diff --git a/rules/ad-hoc-sub-process.js b/rules/ad-hoc-sub-process.js new file mode 100644 index 0000000..791dc71 --- /dev/null +++ b/rules/ad-hoc-sub-process.js @@ -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 is not allowed in '); + } + + if (is(flowElement, 'bpmn:EndEvent')) { + reporter.report(flowElement.id, 'An is not allowed in '); + } + + if (isAny(flowElement, [ 'bpmn:IntermediateCatchEvent', 'bpmn:IntermediateThrowEvent' ])) { + if (!flowElement.outgoing || flowElement.outgoing.length === 0) { + reporter.report(flowElement.id, 'An intermediate event inside must have an outgoing sequence flow'); + } + } + + }); + } + + return { + check + }; + +}; \ No newline at end of file diff --git a/test/integration/compilation/test/bpmnlintrc.expected.js b/test/integration/compilation/test/bpmnlintrc.expected.js index a53770e..0144c83 100644 --- a/test/integration/compilation/test/bpmnlintrc.expected.js +++ b/test/integration/compilation/test/bpmnlintrc.expected.js @@ -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", @@ -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; \ No newline at end of file +cache['bpmnlint-plugin-exported/baz'] = rule_27; + +import rule_28 from 'bpmnlint-plugin-exported/src/foo'; + +cache['bpmnlint-plugin-exported/foo-absolute'] = rule_28; \ No newline at end of file diff --git a/test/rules/ad-hoc-sub-process.mjs b/test/rules/ad-hoc-sub-process.mjs new file mode 100644 index 0000000..06221fd --- /dev/null +++ b/test/rules/ad-hoc-sub-process.mjs @@ -0,0 +1,50 @@ +import RuleTester from '../../lib/testers/rule-tester.js'; + +import rule from '../../rules/ad-hoc-sub-process.js'; + +import { + readModdle +} from '../../lib/testers/helper.js'; + +import { stubCJS } from '../helper.mjs'; + +const { + __dirname +} = stubCJS(import.meta.url); + + +RuleTester.verify('ad-hoc-sub-process', rule, { + valid: [ + { + moddleElement: readModdle(__dirname + '/ad-hoc-sub-process/valid.bpmn') + } + ], + invalid: [ + { + moddleElement: readModdle(__dirname + '/ad-hoc-sub-process/invalid-start-end.bpmn'), + report: [ + { + id: 'StartEvent', + message: 'A is not allowed in ' + }, + { + id: 'EndEvent', + message: 'An is not allowed in ' + } + ] + }, + { + moddleElement: readModdle(__dirname + '/ad-hoc-sub-process/invalid-intermediate.bpmn'), + report: [ + { + id: 'ThrowEvent', + message: 'An intermediate event inside must have an outgoing sequence flow' + }, + { + id: 'CatchEvent', + message: 'An intermediate event inside must have an outgoing sequence flow' + } + ] + } + ] +}); \ No newline at end of file diff --git a/test/rules/ad-hoc-sub-process/invalid-intermediate.bpmn b/test/rules/ad-hoc-sub-process/invalid-intermediate.bpmn new file mode 100644 index 0000000..416bf56 --- /dev/null +++ b/test/rules/ad-hoc-sub-process/invalid-intermediate.bpmn @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/rules/ad-hoc-sub-process/invalid-start-end.bpmn b/test/rules/ad-hoc-sub-process/invalid-start-end.bpmn new file mode 100644 index 0000000..05f2b90 --- /dev/null +++ b/test/rules/ad-hoc-sub-process/invalid-start-end.bpmn @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/rules/ad-hoc-sub-process/valid.bpmn b/test/rules/ad-hoc-sub-process/valid.bpmn new file mode 100644 index 0000000..1a98ade --- /dev/null +++ b/test/rules/ad-hoc-sub-process/valid.bpmn @@ -0,0 +1,53 @@ + + + + + + Flow_1h8yyeu + + + + Flow_0wahprq + + + Flow_1h8yyeu + + + Flow_0wahprq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +