From 4eb57f1e42e747f5d996470f6588fe7d765dbdb2 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 18 Aug 2022 10:45:55 +0200 Subject: [PATCH] feat(rules): add `duplicate-task-headers` rule Related to https://github.com/camunda/linting/issues/4 Co-authored-by: Niklas Kiefer --- index.js | 5 + rules/duplicate-task-headers.js | 54 +++ rules/utils/element.js | 17 +- .../duplicate-task-headers.spec.js | 398 ++++++++++++++++++ test/configs.spec.js | 5 + 5 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 rules/duplicate-task-headers.js create mode 100644 test/camunda-cloud/duplicate-task-headers.spec.js diff --git a/index.js b/index.js index f3d316e4..a5508198 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ module.exports = { rules: { 'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud10 ], 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': [ 'error', elementTypeConfig.camundaCloud10 ], 'error-reference': 'error', 'loop-characteristics': 'error', @@ -20,6 +21,7 @@ module.exports = { rules: { 'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud11 ], 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': [ 'error', elementTypeConfig.camundaCloud11 ], 'error-reference': 'error', 'loop-characteristics': 'error', @@ -33,6 +35,7 @@ module.exports = { rules: { 'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud12 ], 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': [ 'error', elementTypeConfig.camundaCloud12 ], 'error-reference': 'error', 'loop-characteristics': 'error', @@ -46,6 +49,7 @@ module.exports = { rules: { 'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud13 ], 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': [ 'error', elementTypeConfig.camundaCloud12 ], 'error-reference': 'error', 'loop-characteristics': 'error', @@ -59,6 +63,7 @@ module.exports = { rules: { 'called-decision-or-task-definition': [ 'error', calledDecisionOrTaskDefinitionConfig.camundaCloud13 ], 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': [ 'error', elementTypeConfig.camundaCloud12 ], 'error-reference': 'error', 'loop-characteristics': 'error', diff --git a/rules/duplicate-task-headers.js b/rules/duplicate-task-headers.js new file mode 100644 index 00000000..4e2bbd89 --- /dev/null +++ b/rules/duplicate-task-headers.js @@ -0,0 +1,54 @@ +const { is } = require('bpmnlint-utils'); + +const { + findExtensionElement, + getMessageEventDefinition, + hasDuplicatedPropertyValues +} = require('./utils/element'); + +const { reportErrors } = require('./utils/reporter'); + +module.exports = function() { + function check(node, reporter) { + if (!is(node, 'bpmn:UserTask') && !isZeebeServiceTask(node)) { + return; + } + + const taskHeaders = findExtensionElement(node, 'zeebe:TaskHeaders'); + + if (!taskHeaders) { + return; + } + + const errors = hasDuplicatedPropertyValues(taskHeaders, 'values', 'key', node); + + if (errors && errors.length) { + reportErrors(node, reporter, errors); + } + } + + return { + check + }; +}; + + +// helper /////////////// + +function isZeebeServiceTask(element) { + if (!is(element, 'zeebe:ZeebeServiceTask')) { + return false; + } + + if (is(element, 'bpmn:EndEvent') || is(element, 'bpmn:IntermediateThrowEvent')) { + return !!getMessageEventDefinition(element); + } + + // A BusinessRuleTask is per default not a ServiceTask, only if it has a TaskDefinition + // (ie. if the implementation is set to ==JobWorker) + if (is(element, 'bpmn:BusinessRuleTask') && !findExtensionElement(element, 'zeebe:TaskDefinition')) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/rules/utils/element.js b/rules/utils/element.js index db3e56cf..1e71bf50 100644 --- a/rules/utils/element.js +++ b/rules/utils/element.js @@ -5,7 +5,10 @@ const { some } = require('min-dash'); -const { isAny } = require('bpmnlint-utils'); +const { + is, + isAny +} = require('bpmnlint-utils'); const { getPath } = require('@bpmn-io/moddle-utils'); @@ -13,12 +16,22 @@ const { ERROR_TYPES } = require('./error-types'); module.exports.ERROR_TYPES = ERROR_TYPES; -module.exports.getEventDefinition = function(node) { +function getEventDefinition(node) { const eventDefinitions = node.get('eventDefinitions'); if (eventDefinitions) { return eventDefinitions[ 0 ]; } +} + +module.exports.getEventDefinition = getEventDefinition; + +module.exports.getMessageEventDefinition = function(node) { + if (is(node, 'bpmn:ReceiveTask')) { + return node; + } + + return getEventDefinition(node); }; function findExtensionElements(node, types) { diff --git a/test/camunda-cloud/duplicate-task-headers.spec.js b/test/camunda-cloud/duplicate-task-headers.spec.js new file mode 100644 index 00000000..b52e5628 --- /dev/null +++ b/test/camunda-cloud/duplicate-task-headers.spec.js @@ -0,0 +1,398 @@ +const RuleTester = require('bpmnlint/lib/testers/rule-tester'); + +const rule = require('../../rules/duplicate-task-headers'); + +const { + createModdle, + createProcess +} = require('../helper'); + +const { ERROR_TYPES } = require('../../rules/utils/element'); + +const valid = [ + { + name: 'service task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'send task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'user task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'business rule task', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)) + }, + { + name: 'script task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)) + }, + { + name: 'message throw event', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)) + } +]; + +const invalid = [ + { + name: 'service task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'ServiceTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ServiceTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'service task (multiple duplicates)', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)), + report: [ + { + id: 'ServiceTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ServiceTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + ] + }, + { + name: 'service task (multiple duplicates)', + moddleElement: createModdle(createProcess(` + + + + + + + + + + + `)), + report: [ + { + id: 'ServiceTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ServiceTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + }, + { + id: 'ServiceTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ServiceTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'bar', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + ] + }, + { + name: 'service task (no key)', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'ServiceTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ServiceTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: undefined, + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'send task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'SendTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'SendTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'user task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'UserTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'UserTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'business rule task', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)), + report: { + id: 'BusinessRuleTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'BusinessRuleTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'script task', + moddleElement: createModdle(createProcess(` + + + + + + + + + `)), + report: { + id: 'ScriptTask_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'ScriptTask_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + }, + { + name: 'message throw event', + moddleElement: createModdle(createProcess(` + + + + + + + + + + `)), + report: { + id: 'MessageEvent_1', + message: 'Properties of type have property with duplicate value of ', + path: null, + error: { + type: ERROR_TYPES.PROPERTY_VALUE_DUPLICATED, + node: 'zeebe:TaskHeaders', + parentNode: 'MessageEvent_1', + duplicatedProperty: 'key', + duplicatedPropertyValue: 'foo', + properties: [ + 'zeebe:Header', + 'zeebe:Header' + ], + propertiesName: 'values' + } + } + } +]; + +RuleTester.verify('task-headers', rule, { + valid, + invalid +}); \ No newline at end of file diff --git a/test/configs.spec.js b/test/configs.spec.js index a1c73bae..6ad90a4f 100644 --- a/test/configs.spec.js +++ b/test/configs.spec.js @@ -7,6 +7,7 @@ describe('configs', function() { it('camunda-cloud-1-0', expectRules(configs, 'camunda-cloud-1-0', { 'called-decision-or-task-definition': 'error', 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': 'error', 'error-reference': 'error', 'loop-characteristics': 'error', @@ -20,6 +21,7 @@ describe('configs', function() { it('camunda-cloud-1-1', expectRules(configs, 'camunda-cloud-1-1', { 'called-decision-or-task-definition': 'error', 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': 'error', 'error-reference': 'error', 'loop-characteristics': 'error', @@ -33,6 +35,7 @@ describe('configs', function() { it('camunda-cloud-1-2', expectRules(configs, 'camunda-cloud-1-2', { 'called-decision-or-task-definition': 'error', 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': 'error', 'error-reference': 'error', 'loop-characteristics': 'error', @@ -46,6 +49,7 @@ describe('configs', function() { it('camunda-cloud-1-3', expectRules(configs, 'camunda-cloud-1-3', { 'called-decision-or-task-definition': 'error', 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': 'error', 'error-reference': 'error', 'loop-characteristics': 'error', @@ -59,6 +63,7 @@ describe('configs', function() { it('camunda-cloud-8-0', expectRules(configs, 'camunda-cloud-8-0', { 'called-decision-or-task-definition': 'error', 'called-element': 'error', + 'duplicate-task-headers': 'error', 'element-type': 'error', 'error-reference': 'error', 'loop-characteristics': 'error',