From 0c71398c8f1c88b1b37d158dab39f2096326aede 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 | 57 +++ rules/user-task-form.js | 2 + rules/utils/element.js | 17 +- .../duplicate-task-headers.spec.js | 398 ++++++++++++++++++ test/configs.spec.js | 5 + 6 files changed, 482 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..7ec5b2b6 --- /dev/null +++ b/rules/duplicate-task-headers.js @@ -0,0 +1,57 @@ +const { + is, + isAny +} = 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 + }; +}; + +// helpers ////////// + +function isZeebeServiceTask(element) { + if (is(element, 'zeebe:ZeebeServiceTask')) { + return true; + } + + if (isAny(element, [ + 'bpmn:EndEvent', + 'bpmn:IntermediateThrowEvent' + ])) { + return getMessageEventDefinition(element); + } + + if (is(element, 'bpmn:BusinessRuleTask')) { + return findExtensionElement(element, 'zeebe:TaskDefinition'); + } + + return false; +} \ No newline at end of file diff --git a/rules/user-task-form.js b/rules/user-task-form.js index 31c25f1b..700fbc7c 100644 --- a/rules/user-task-form.js +++ b/rules/user-task-form.js @@ -56,6 +56,8 @@ module.exports = function() { }; }; +// helpers ////////// + function findUserTaskForm(node, formKey) { const process = findParent(node, 'bpmn:Process'); diff --git a/rules/utils/element.js b/rules/utils/element.js index 6e1c994b..faa002d1 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',