Skip to content

Commit

Permalink
feat(rules): implement global rule
Browse files Browse the repository at this point in the history
A rule that verifies that global elements are properly used.

Currently recognized global elements are:

  * `bpmn:Error`
  * `bpmn:Escalation`
  * `bpmn:Signal`
  * `bpmn:Message`

For each of these elements proper usage implies:

  * element must have a name
  * element is used (referenced) from event definitions
  * there exists only a single element per type with a given name

Related to camunda/camunda-modeler#4339
  • Loading branch information
strangelookingnerd authored and nikku committed Jun 20, 2024
1 parent 74bceb4 commit d1e37ff
Show file tree
Hide file tree
Showing 22 changed files with 1,100 additions and 46 deletions.
1 change: 1 addition & 0 deletions config/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const allRules = [
'end-event-required',
'event-sub-process-typed-start-event',
'fake-join',
'global',
'label-required',
'link-event',
'no-bpmndi',
Expand Down
1 change: 1 addition & 0 deletions config/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
'end-event-required': 'error',
'event-sub-process-typed-start-event': 'error',
'fake-join': 'warn',
'global': 'warn',
'label-required': 'error',
'link-event': 'error',
'no-bpmndi': 'error',
Expand Down
117 changes: 117 additions & 0 deletions rules/global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const {
isAny, is
} = require('bpmnlint-utils');


/**
* A rule that verifies that global elements are properly used.
*
* Currently recognized global elements are:
*
* * `bpmn:Error`
* * `bpmn:Escalation`
* * `bpmn:Signal`
* * `bpmn:Message`
*
* For each of these elements proper usage implies:
*
* * element must have a name
* * element is used (referenced) from event definitions
* * there exists only a single element per type with a given name
*/
module.exports = function() {

function check(node, reporter) {

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

const events = getEvents(node);
const eventDefinitions = getEventDefinitions(node);

events.forEach(event => {
if (!hasName(event)) {
reporter.report(event.id, 'Element is missing name');
}

if (!isReferenced(event, eventDefinitions)) {
reporter.report(event.id, 'Element is unused');
}

if (!isUnique(event, events)) {
reporter.report(event.id, 'Element name is not unique');
}
});

}

return {
check
};

// helpers /////////////////////////////

function getEvents(definition) {
return definition.rootElements.filter(node => isAny(node, [ 'bpmn:Error', 'bpmn:Escalation', 'bpmn:Message', 'bpmn:Signal' ]));
}

function getEventDefinitions(definition) {
const eventDefinitions = [];

function traverse(element) {
if (element.rootElements) {
element.rootElements.forEach(traverse);
}

if (element.flowElements) {
element.flowElements.forEach(traverse);
}

if (element.eventDefinitions) {
element.eventDefinitions.forEach(eventDefinition => eventDefinitions.push(eventDefinition));
}
}

traverse(definition);
return eventDefinitions;
}

function hasName(event) {
return (
event.name?.trim() !== ''
);
}

function isReferenced(event, eventDefinitions) {
if (is(event, 'bpmn:Error')) {
return (
eventDefinitions.some(node => is(node, 'bpmn:ErrorEventDefinition') && event.id === node.errorRef?.id)
);
}

if (is(event, 'bpmn:Escalation')) {
return (
eventDefinitions.some(node => is(node, 'bpmn:EscalationEventDefinition') && event.id === node.escalationRef?.id)
);
}

if (is(event, 'bpmn:Message')) {
return (
eventDefinitions.some(node => is(node, 'bpmn:MessageEventDefinition') && event.id === node.messageRef?.id)
);
}

if (is(event, 'bpmn:Signal')) {
return (
eventDefinitions.some(node => is(node, 'bpmn:SignalEventDefinition') && event.id === node.signalRef?.id)
);
}
}

function isUnique(event, events) {
return (
events.filter(node => is(node, event.$type) && event.name === node.name).length === 1
);
}
};
97 changes: 51 additions & 46 deletions test/integration/compilation/test/bpmnlintrc.expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const rules = {
"end-event-required": "error",
"event-sub-process-typed-start-event": "error",
"fake-join": "warn",
"global": "warn",
"label-required": "error",
"link-event": "error",
"no-bpmndi": "error",
Expand Down Expand Up @@ -87,94 +88,98 @@ import rule_3 from 'bpmnlint/rules/fake-join';

cache['bpmnlint/fake-join'] = rule_3;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

0 comments on commit d1e37ff

Please sign in to comment.