diff --git a/index.js b/index.js index 6cf6ff9..2002a4f 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ module.exports = { 'processmaker/call-activity-child-process': 'error', 'processmaker/call-activity-sequence-flow': 'error', 'processmaker/id-required': 'error', + 'processmaker/signal-ref-required': 'error', }, }, }, diff --git a/rules/id-required.js b/rules/id-required.js index 3c6ddfd..576e992 100644 --- a/rules/id-required.js +++ b/rules/id-required.js @@ -1,4 +1,4 @@ -const { is } = require('bpmnlint-utils'); +const { is, isAny } = require('bpmnlint-utils'); /** * A rule that checks the presence of a node ID. @@ -8,8 +8,26 @@ module.exports = function() { return node.$type.startsWith('bpmndi') || node.$type.startsWith('dc'); } + function isEventDefinition(node) { + return isAny(node, [ + 'bpmn:CancelEventDefinition', + 'bpmn:CompensateDefinition', + 'bpmn:ErrorEventDefinition', + 'bpmn:EscalationEventDefinition', + 'bpmn:Expression', + 'bpmn:LinkEventDefinition', + 'bpmn:MessageEventDefinition', + 'bpmn:SignalEventDefinition', + 'bpmn:TerminateEventDefinition', + 'bpmn:TimeCycle', + 'bpmn:TimeDate', + 'bpmn:TimeDuration', + 'bpmn:TimerEventDefinition', + ]); + } + function check(node, reporter) { - if (is(node, 'bpmn:Definitions') || isNonBpmnType(node)) { + if (is(node, 'bpmn:Definitions') || isNonBpmnType(node) || isEventDefinition(node)) { return; } diff --git a/rules/signal-ref-required.js b/rules/signal-ref-required.js new file mode 100644 index 0000000..74b8e26 --- /dev/null +++ b/rules/signal-ref-required.js @@ -0,0 +1,43 @@ +const { is, isAny } = require('bpmnlint-utils'); + +/** + * A rule that checks that signal events have a signal ref + */ +module.exports = function() { + + function hasEventDefinitions(node) { + return Array.isArray(node.eventDefinitions) + && node.eventDefinitions.length > 0; + } + + function filterEventDefinitionsWithoutSignalRef(node) { + return node.eventDefinitions.filter( + eventDefinition => is(eventDefinition, 'bpmn:SignalEventDefinition') + && !eventDefinition.signalRef + ); + } + + function check(node, reporter) { + if (!isAny(node, [ + 'bpmn:StartEvent', + 'bpmn:EndEvent', + 'bpmn:IntermediateCatchEvent', + 'bpmn:IntermediateThrowEvent', + 'bpmn:BoundaryEvent', + ])) { + return; + } + + if (!hasEventDefinitions(node)) { + return; + } + + const missing = filterEventDefinitionsWithoutSignalRef(node); + + if (missing.length > 0) { + reporter.report(node.id, 'Missing signal reference'); + } + } + + return { check }; +}; diff --git a/test-diagrams/boundary-event-signal-ref.invalid.bpmn b/test-diagrams/boundary-event-signal-ref.invalid.bpmn new file mode 100644 index 0000000..9ad7df9 --- /dev/null +++ b/test-diagrams/boundary-event-signal-ref.invalid.bpmn @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test-diagrams/boundary-event-signal-ref.valid.bpmn b/test-diagrams/boundary-event-signal-ref.valid.bpmn new file mode 100644 index 0000000..d59aadc --- /dev/null +++ b/test-diagrams/boundary-event-signal-ref.valid.bpmn @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test-diagrams/end-event-signal-ref.invalid.bpmn b/test-diagrams/end-event-signal-ref.invalid.bpmn new file mode 100644 index 0000000..08a31a4 --- /dev/null +++ b/test-diagrams/end-event-signal-ref.invalid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/end-event-signal-ref.valid.bpmn b/test-diagrams/end-event-signal-ref.valid.bpmn new file mode 100644 index 0000000..8e74ed0 --- /dev/null +++ b/test-diagrams/end-event-signal-ref.valid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/event-definitions.valid.bpmn b/test-diagrams/event-definitions.valid.bpmn new file mode 100644 index 0000000..31888ed --- /dev/null +++ b/test-diagrams/event-definitions.valid.bpmn @@ -0,0 +1,58 @@ + + + + + + + + + 2020-04-17T20:19:00.000Z|R/2020-04-17T20:19:00.000Z/P1W + + + + + + + + 2020-04-17T20:34:00.000Z + + + + + + + + + + + PT1H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-diagrams/intermediate-catch-event-signal-ref.invalid.bpmn b/test-diagrams/intermediate-catch-event-signal-ref.invalid.bpmn new file mode 100644 index 0000000..7f3042f --- /dev/null +++ b/test-diagrams/intermediate-catch-event-signal-ref.invalid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/intermediate-catch-event-signal-ref.valid.bpmn b/test-diagrams/intermediate-catch-event-signal-ref.valid.bpmn new file mode 100644 index 0000000..c3b8e94 --- /dev/null +++ b/test-diagrams/intermediate-catch-event-signal-ref.valid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/intermediate-throw-event-signal-ref.invalid.bpmn b/test-diagrams/intermediate-throw-event-signal-ref.invalid.bpmn new file mode 100644 index 0000000..72c96f6 --- /dev/null +++ b/test-diagrams/intermediate-throw-event-signal-ref.invalid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/intermediate-throw-event-signal-ref.valid.bpmn b/test-diagrams/intermediate-throw-event-signal-ref.valid.bpmn new file mode 100644 index 0000000..efe0753 --- /dev/null +++ b/test-diagrams/intermediate-throw-event-signal-ref.valid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/start-event-signal-ref.invalid.bpmn b/test-diagrams/start-event-signal-ref.invalid.bpmn new file mode 100644 index 0000000..2c767ed --- /dev/null +++ b/test-diagrams/start-event-signal-ref.invalid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test-diagrams/start-event-signal-ref.valid.bpmn b/test-diagrams/start-event-signal-ref.valid.bpmn new file mode 100644 index 0000000..d00c270 --- /dev/null +++ b/test-diagrams/start-event-signal-ref.valid.bpmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/test.js b/test.js index 98f04b2..932e796 100644 --- a/test.js +++ b/test.js @@ -5,6 +5,7 @@ const gatewayDirectionRule = require('./rules/gateway-direction'); const callActivityChildProcessRule = require('./rules/call-activity-child-process'); const callActivitySequenceFlowRule = require('./rules/call-activity-sequence-flow'); const idRequiredRule = require('./rules/id-required'); +const signalRefRequiredRule = require('./rules/signal-ref-required'); RuleTester.verify('gateway-direction', gatewayDirectionRule, { valid: [ @@ -85,6 +86,9 @@ RuleTester.verify('id-required', idRequiredRule, { valid: [ { moddleElement: readModdle('./test-diagrams/id-required.valid.bpmn') + }, + { + moddleElement: readModdle('./test-diagrams/event-definitions.valid.bpmn') } ], invalid: [ @@ -104,3 +108,60 @@ RuleTester.verify('id-required', idRequiredRule, { } ] }); + +RuleTester.verify('signal-ref-required', signalRefRequiredRule, { + valid: [ + { + moddleElement: readModdle('./test-diagrams/start-event-signal-ref.valid.bpmn') + }, + { + moddleElement: readModdle('./test-diagrams/intermediate-catch-event-signal-ref.valid.bpmn') + }, + { + moddleElement: readModdle('./test-diagrams/intermediate-throw-event-signal-ref.valid.bpmn') + }, + { + moddleElement: readModdle('./test-diagrams/boundary-event-signal-ref.valid.bpmn') + }, + { + moddleElement: readModdle('./test-diagrams/end-event-signal-ref.valid.bpmn') + } + ], + invalid: [ + { + moddleElement: readModdle('./test-diagrams/start-event-signal-ref.invalid.bpmn'), + report: { + id: 'node_2', + message: 'Missing signal reference' + } + }, + { + moddleElement: readModdle('./test-diagrams/intermediate-catch-event-signal-ref.invalid.bpmn'), + report: { + id: 'node_2', + message: 'Missing signal reference' + } + }, + { + moddleElement: readModdle('./test-diagrams/intermediate-throw-event-signal-ref.invalid.bpmn'), + report: { + id: 'node_2', + message: 'Missing signal reference' + } + }, + { + moddleElement: readModdle('./test-diagrams/boundary-event-signal-ref.invalid.bpmn'), + report: { + id: 'node_2', + message: 'Missing signal reference' + } + }, + { + moddleElement: readModdle('./test-diagrams/end-event-signal-ref.invalid.bpmn'), + report: { + id: 'node_2', + message: 'Missing signal reference' + } + }, + ] +});