@camunda/linting 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to [@camunda/linting](https://github.com/camunda/linting) are documented here. We use [semantic versioning](http://semver.org/) for releases.
4
+
5
+ ## Unreleased
6
+
7
+ ___Note:__ Yet to be released changes appear here._
8
+
9
+ ## 0.1.0
10
+
11
+ * `FEAT`: initial release bundling [bpmnlint](https://github.com/bpmn-io/bpmnlint) and [bpmnlint-plugin-camunda-compat](https://github.com/camunda/bpmnlint-plugin-camunda-compat/) and Camunda specific functionality ([#1](https://github.com/camunda/linting/pull/1))
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @camunda/linting
2
+
3
+ The BPMN linter used by the Camunda Desktop and Web Modeler. Batteries included. 🔋
4
+
5
+ # Features
6
+
7
+ * bundles [bpmnlint](https://github.com/bpmn-io/bpmnlint) and [bpmnlint-plugin-camunda-compat](https://github.com/camunda/bpmnlint-plugin-camunda-compat/)
8
+ * configures linter based on `modeler:executionPlatform` and `modeler:executionPlatformVersion`
9
+ * adjusts error messages to be shown in desktop and web modeler
10
+
11
+ # Usage
12
+
13
+ ```javascript
14
+ import { Linter } from '@camunda/linting';
15
+
16
+ ...
17
+
18
+ // lint by passing definitions
19
+ const reports = await Linter.lint(definitions);
20
+
21
+ // or passing XML
22
+ const reports = await Linter.lint(xml);
23
+ ```
24
+
25
+ # License
26
+
27
+ MIT
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export { Linter } from './lib/Linter';
package/lib/Linter.js ADDED
@@ -0,0 +1,116 @@
1
+ import BpmnModdle from 'bpmn-moddle/dist/index';
2
+
3
+ import { Linter as BpmnLinter } from 'bpmnlint';
4
+
5
+ import StaticResolver from 'bpmnlint/lib/resolver/static-resolver';
6
+
7
+ import { configs } from 'bpmnlint-plugin-camunda-compat';
8
+
9
+ import { isString } from 'min-dash';
10
+
11
+ import modelerModdle from 'modeler-moddle/resources/modeler.json';
12
+ import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe.json';
13
+
14
+ import { adjustErrorMessages } from './utils/error-messages';
15
+
16
+ const moddle = new BpmnModdle({
17
+ modeler: modelerModdle,
18
+ zeebe: zeebeModdle
19
+ });
20
+
21
+ export class Linter {
22
+ static async lint(contents) {
23
+ let rootElement;
24
+
25
+ if (isString(contents)) {
26
+ ({ rootElement } = await moddle.fromXML(contents));
27
+ } else {
28
+ rootElement = contents;
29
+ }
30
+
31
+ const executionPlatform = rootElement.get('modeler:executionPlatform'),
32
+ executionPlatformVersion = rootElement.get('modeler:executionPlatformVersion');
33
+
34
+ if (!executionPlatform || !executionPlatformVersion) {
35
+ return [];
36
+ }
37
+
38
+ const configName = getConfigName(executionPlatform, executionPlatformVersion);
39
+
40
+ const config = configs[ configName ];
41
+
42
+ if (!config) {
43
+ return [];
44
+ }
45
+
46
+ const rules = await importRules(config);
47
+
48
+ const linter = new BpmnLinter({
49
+ config: prefixRules(config),
50
+ resolver: new StaticResolver(rules)
51
+ });
52
+
53
+ let reports = await linter.lint(rootElement);
54
+
55
+ reports = adjustErrorMessages(reports, getExecutionPlatformLabel(executionPlatform, toSemverMinor(executionPlatformVersion)));
56
+
57
+ return Object.values(reports).reduce((reports, report) => {
58
+ return [ ...reports, ...report ];
59
+ }, []);
60
+ }
61
+ }
62
+
63
+ function getConfigName(executionPlatform, executionPlatformVersion) {
64
+ return [
65
+ ...executionPlatform.split(' ').map(toLowerCase),
66
+ ...toSemverMinor(executionPlatformVersion).split('.')
67
+ ].join('-');
68
+ }
69
+
70
+ function prefixRules({ rules }) {
71
+ return {
72
+ rules: Object.entries(rules).reduce((rules, [ key, value ]) => {
73
+ return {
74
+ ...rules,
75
+ [ `bpmnlint-plugin-camunda-compat/${ key }` ]: value
76
+ };
77
+ }, {})
78
+ };
79
+ }
80
+
81
+ async function importRules({ rules }) {
82
+ let importedRules = {};
83
+
84
+ for (let key of Object.keys(rules)) {
85
+ const importedRule = require(`bpmnlint-plugin-camunda-compat/rules/${ key }`);
86
+
87
+ importedRules = {
88
+ ...importedRules,
89
+ [ `rule:bpmnlint-plugin-camunda-compat/${ key }` ]: importedRule
90
+ };
91
+ }
92
+
93
+ return importedRules;
94
+ }
95
+
96
+ const executionPlatformLabels = {
97
+ 'Camunda Cloud': {
98
+ '1.0': 'Camunda 8 (Zeebe 1.0)',
99
+ '1.1': 'Camunda 8 (Zeebe 1.1)',
100
+ '1.2': 'Camunda 8 (Zeebe 1.2)',
101
+ '1.3': 'Camunda 8 (Zeebe 1.3)',
102
+ '8.0': 'Camunda 8'
103
+ }
104
+ };
105
+
106
+ function getExecutionPlatformLabel(executionPlatform, executionPlatformVersion) {
107
+ return executionPlatformLabels[ executionPlatform ] && executionPlatformLabels[ executionPlatform ][ executionPlatformVersion ];
108
+ }
109
+
110
+ function toLowerCase(string) {
111
+ return string.toLowerCase();
112
+ }
113
+
114
+ function toSemverMinor(executionPlatformVersion) {
115
+ return executionPlatformVersion.split('.').slice(0, 2).join('.');
116
+ }
@@ -0,0 +1,247 @@
1
+ import { ERROR_TYPES } from 'bpmnlint-plugin-camunda-compat/rules/utils/element';
2
+
3
+ import { is } from 'bpmnlint-utils';
4
+
5
+ import { isArray } from 'min-dash';
6
+
7
+ import { getTypeString } from './types';
8
+
9
+ export function adjustErrorMessage(report, executionPlatformLabel) {
10
+ const {
11
+ error,
12
+ message
13
+ } = report;
14
+
15
+ if (!error) {
16
+ return report;
17
+ }
18
+
19
+ const { type } = error;
20
+
21
+ if (type === ERROR_TYPES.ELEMENT_TYPE_NOT_ALLOWED) {
22
+ return getElementTypeNotAllowedErrorMessage(report, executionPlatformLabel);
23
+ }
24
+
25
+ if (type === ERROR_TYPES.EXTENSION_ELEMENT_NOT_ALLOWED) {
26
+ return getExtensionElementNotAllowedErrorMessage(report, executionPlatformLabel);
27
+ }
28
+
29
+ if (type === ERROR_TYPES.EXTENSION_ELEMENT_REQUIRED) {
30
+ return getExtensionElementRequiredErrorMessage(report);
31
+ }
32
+
33
+ if (type === ERROR_TYPES.PROPERTY_DEPENDEND_REQUIRED) {
34
+ return getPropertyDependendRequiredErrorMessage(report);
35
+ }
36
+
37
+ if (type === ERROR_TYPES.PROPERTY_REQUIRED) {
38
+ return getPropertyRequiredErrorMessage(report, executionPlatformLabel);
39
+ }
40
+
41
+ if (type === ERROR_TYPES.PROPERTY_TYPE_NOT_ALLOWED) {
42
+ return getPropertyTypeNotAllowedErrorMessage(report, executionPlatformLabel);
43
+ }
44
+
45
+ return message;
46
+ }
47
+
48
+ export function adjustErrorMessages(reports, executionPlatformLabel) {
49
+ Object.values(reports).forEach(reportsForRule => {
50
+ reportsForRule.forEach(report => {
51
+ report.message = adjustErrorMessage(report, executionPlatformLabel);
52
+ });
53
+ });
54
+
55
+ return reports;
56
+ }
57
+
58
+ function getIndefiniteArticle(type) {
59
+ if ([
60
+ 'Error',
61
+ 'Escalation',
62
+ 'Event',
63
+ 'Intermediate',
64
+ 'Undefined'
65
+ ].includes(type.split(' ')[ 0 ])) {
66
+ return 'An';
67
+ }
68
+
69
+ return 'A';
70
+ }
71
+
72
+ function getElementTypeNotAllowedErrorMessage(report, executionPlatformLabel) {
73
+ const { error } = report;
74
+
75
+ const { node } = error;
76
+
77
+ const typeString = getTypeString(node);
78
+
79
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> is not supported by ${ executionPlatformLabel }`;
80
+ }
81
+
82
+ function getExtensionElementNotAllowedErrorMessage(report, executionPlatformLabel) {
83
+ const {
84
+ error,
85
+ message
86
+ } = report;
87
+
88
+ const {
89
+ node,
90
+ extensionElement
91
+ } = error;
92
+
93
+ if (is(node, 'bpmn:BusinessRuleTask') && is(extensionElement, 'zeebe:CalledDecision')) {
94
+ return `A <Business Rule Task> with <Implementation: DMN decision> is not supported by ${ executionPlatformLabel }`;
95
+ }
96
+
97
+ return message;
98
+ }
99
+
100
+ function getExtensionElementRequiredErrorMessage(report) {
101
+ const {
102
+ error,
103
+ message
104
+ } = report;
105
+
106
+ const {
107
+ node,
108
+ parentNode,
109
+ requiredExtensionElement
110
+ } = error;
111
+
112
+ const typeString = getTypeString(parentNode || node);
113
+
114
+ if (requiredExtensionElement === 'zeebe:CalledElement') {
115
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a defined <Called element>`;
116
+ }
117
+
118
+ if (requiredExtensionElement === 'zeebe:LoopCharacteristics') {
119
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Multi-instance marker> must have a defined <Input collection>`;
120
+ }
121
+
122
+ if (requiredExtensionElement === 'zeebe:Subscription') {
123
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Message Reference> must have a defined <Subscription correlation key>`;
124
+ }
125
+
126
+ if (requiredExtensionElement === 'zeebe:TaskDefinition') {
127
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a <Task definition type>`;
128
+ }
129
+
130
+ if (isArray(requiredExtensionElement) && requiredExtensionElement.includes('zeebe:CalledDecision')) {
131
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a defined <Implementation>`;
132
+ }
133
+
134
+ return message;
135
+ }
136
+
137
+ function getPropertyDependendRequiredErrorMessage(report) {
138
+ const {
139
+ error,
140
+ message
141
+ } = report;
142
+
143
+ const {
144
+ node,
145
+ parentNode,
146
+ property,
147
+ dependendRequiredProperty
148
+ } = error;
149
+
150
+ const typeString = getTypeString(parentNode || node);
151
+
152
+ if (is(node, 'zeebe:LoopCharacteristics') && property === 'outputCollection' && dependendRequiredProperty === 'outputElement') {
153
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Multi-instance marker> and defined <Output collection> must have a defined <Output element>`;
154
+ }
155
+
156
+ if (is(node, 'zeebe:LoopCharacteristics') && property === 'outputElement' && dependendRequiredProperty === 'outputCollection') {
157
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Multi-instance marker> and defined <Output element> must have a defined <Output collection>`;
158
+ }
159
+
160
+ return message;
161
+ }
162
+
163
+ function getPropertyRequiredErrorMessage(report, executionPlatformLabel) {
164
+ const {
165
+ error,
166
+ message
167
+ } = report;
168
+
169
+ const {
170
+ node,
171
+ parentNode,
172
+ requiredProperty
173
+ } = error;
174
+
175
+ const typeString = getTypeString(parentNode || node);
176
+
177
+ if (parentNode && is(parentNode, 'bpmn:BusinessRuleTask') && is(node, 'zeebe:CalledDecision') && requiredProperty === 'decisionId') {
178
+ return 'A <Business Rule Task> with <Implementation: DMN decision> must have a defined <Called decision ID>';
179
+ }
180
+
181
+ if (parentNode && is(parentNode, 'bpmn:BusinessRuleTask') && is(node, 'zeebe:CalledDecision') && requiredProperty === 'resultVariable') {
182
+ return 'A <Business Rule Task> with <Implementation: DMN decision> must have a defined <Result variable>';
183
+ }
184
+
185
+ if (parentNode && is(parentNode, 'bpmn:BusinessRuleTask') && is(node, 'zeebe:TaskDefinition') && requiredProperty === 'type') {
186
+ return 'A <Business Rule Task> with <Implementation: Job worker> must have a defined <Task definition type>';
187
+ }
188
+
189
+ if (is(node, 'zeebe:CalledElement') && requiredProperty === 'processId') {
190
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a defined <Called element>`;
191
+ }
192
+
193
+ if (is(node, 'bpmn:Error') && requiredProperty === 'errorCode') {
194
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Error Reference> must have a defined <Error code>`;
195
+ }
196
+
197
+ if (is(node, 'bpmn:Event') && requiredProperty === 'eventDefinitions') {
198
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> is not supported by ${ executionPlatformLabel }`;
199
+ }
200
+
201
+ if (is(node, 'zeebe:LoopCharacteristics') && requiredProperty === 'inputCollection') {
202
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Multi-instance marker> must have a defined <Input collection>`;
203
+ }
204
+
205
+ if (is(node, 'bpmn:Message') && requiredProperty === 'name') {
206
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Message Reference> must have a defined <Name>`;
207
+ }
208
+
209
+ if (is(node, 'zeebe:Subscription') && requiredProperty === 'correlationKey') {
210
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Message Reference> must have a defined <Subscription correlation key>`;
211
+ }
212
+
213
+ if (is(node, 'zeebe:TaskDefinition') && requiredProperty === 'type') {
214
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> with <Implementation: Job worker> must have a defined <Task definition type>`;
215
+ }
216
+
217
+ if (requiredProperty === 'errorRef') {
218
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a defined <Error Reference>`;
219
+ }
220
+
221
+ if (requiredProperty === 'messageRef') {
222
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> must have a defined <Message Reference>`;
223
+ }
224
+
225
+ return message;
226
+ }
227
+
228
+ function getPropertyTypeNotAllowedErrorMessage(report, executionPlatformLabel) {
229
+ const {
230
+ error,
231
+ message
232
+ } = report;
233
+
234
+ const {
235
+ node,
236
+ parentNode,
237
+ property
238
+ } = error;
239
+
240
+ const typeString = getTypeString(parentNode || node);
241
+
242
+ if (is(node, 'bpmn:Event') && property === 'eventDefinitions') {
243
+ return `${ getIndefiniteArticle(typeString) } <${ typeString }> is not supported by ${ executionPlatformLabel }`;
244
+ }
245
+
246
+ return message;
247
+ }
@@ -0,0 +1,47 @@
1
+ function getEventDefinition(node) {
2
+ const eventDefinitions = node.get('eventDefinitions');
3
+
4
+ return eventDefinitions && eventDefinitions[ 0 ];
5
+ }
6
+
7
+ function getEventDefinitionPrefix(eventDefinition) {
8
+ const localType = getLocalType(eventDefinition);
9
+
10
+ return localType.replace('EventDefinition', '');
11
+ }
12
+
13
+ function getLocalType(node) {
14
+ const { $descriptor } = node;
15
+
16
+ const { localName } = $descriptor.ns;
17
+
18
+ return localName;
19
+ }
20
+
21
+ function formatTypeString(string) {
22
+ return string.replace(/([a-z])([A-Z])/g, '$1 $2').trim();
23
+ }
24
+
25
+ export function getTypeString(node) {
26
+ let type = getLocalType(node);
27
+
28
+ const eventDefinition = getEventDefinition(node);
29
+
30
+ if (eventDefinition) {
31
+ type = `${ getEventDefinitionPrefix(eventDefinition) }${ type }`;
32
+ } else if ([
33
+ 'BoundaryEvent',
34
+ 'EndEvent',
35
+ 'IntermediateCatchEvent',
36
+ 'IntermediateThrowEvent',
37
+ 'StartEvent'
38
+ ].includes(type)) {
39
+ type = `Undefined ${ type }`;
40
+ }
41
+
42
+ if (type === 'Task') {
43
+ type = 'Undefined Task';
44
+ }
45
+
46
+ return formatTypeString(type);
47
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@camunda/linting",
3
+ "version": "0.1.0",
4
+ "description": "Linting for Camunda Platform",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "all": "npm run lint && npm test",
8
+ "lint": "eslint .",
9
+ "test": "mocha -r esm --reporter=spec --recursive test/spec",
10
+ "test:watch": "npm run test -- --watch"
11
+ },
12
+ "keywords": [
13
+ "bpmnlint",
14
+ "camunda"
15
+ ],
16
+ "author": {
17
+ "name": "Philipp Fromme",
18
+ "url": "https://github.com/philippfromme"
19
+ },
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "bpmnlint": "^7.8.0",
23
+ "bpmnlint-plugin-camunda-compat": "^0.7.0",
24
+ "bpmnlint-utils": "^1.0.2"
25
+ },
26
+ "devDependencies": {
27
+ "bpmn-moddle": "^7.1.2",
28
+ "chai": "^4.3.6",
29
+ "eslint": "^4.11.0",
30
+ "eslint-plugin-bpmn-io": "^0.4.1",
31
+ "esm": "^3.2.25",
32
+ "min-dash": "^3.8.1",
33
+ "mocha": "^4.0.1",
34
+ "modeler-moddle": "^0.2.0",
35
+ "zeebe-bpmn-moddle": "^0.12.1"
36
+ },
37
+ "files": [
38
+ "lib"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }