bpmnlint 11.12.1 → 11.13.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.
Files changed (47) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +203 -203
  3. package/bin/bpmnlint.js +437 -437
  4. package/config/all.js +38 -37
  5. package/config/correctness.js +12 -12
  6. package/config/recommended.js +29 -29
  7. package/lib/index.js +4 -4
  8. package/lib/linter.js +542 -542
  9. package/lib/resolver/helper.js +12 -12
  10. package/lib/resolver/node-resolver.js +135 -135
  11. package/lib/resolver/static-resolver.js +47 -47
  12. package/lib/support/compile-config.js +200 -200
  13. package/lib/test-rule.js +107 -107
  14. package/lib/testers/helper.js +72 -72
  15. package/lib/testers/rule-tester.d.ts +36 -36
  16. package/lib/testers/rule-tester.js +146 -146
  17. package/lib/traverse.js +56 -56
  18. package/lib/types.d.ts +80 -80
  19. package/package.json +74 -73
  20. package/rules/ad-hoc-sub-process.js +43 -43
  21. package/rules/conditional-event.js +51 -51
  22. package/rules/conditional-flows.js +57 -57
  23. package/rules/end-event-required.js +45 -45
  24. package/rules/event-based-gateway.js +44 -44
  25. package/rules/event-sub-process-typed-start-event.js +43 -43
  26. package/rules/fake-join.js +40 -40
  27. package/rules/global.js +145 -145
  28. package/rules/helper.js +123 -123
  29. package/rules/label-required.js +75 -75
  30. package/rules/link-event.js +106 -106
  31. package/rules/no-bpmndi.js +157 -157
  32. package/rules/no-complex-gateway.js +2 -2
  33. package/rules/no-disconnected.js +82 -82
  34. package/rules/no-duplicate-sequence-flows.js +69 -69
  35. package/rules/no-gateway-join-fork.js +35 -35
  36. package/rules/no-implicit-end.js +98 -98
  37. package/rules/no-implicit-split.js +55 -55
  38. package/rules/no-implicit-start.js +69 -69
  39. package/rules/no-inclusive-gateway.js +2 -2
  40. package/rules/no-overlapping-elements.js +196 -196
  41. package/rules/single-blank-start-event.js +47 -47
  42. package/rules/single-event-definition.js +33 -33
  43. package/rules/standard-size.js +235 -0
  44. package/rules/start-event-required.js +45 -45
  45. package/rules/sub-process-blank-start-event.js +43 -43
  46. package/rules/superfluous-gateway.js +36 -36
  47. package/rules/superfluous-termination.js +70 -70
package/lib/linter.js CHANGED
@@ -1,543 +1,543 @@
1
- const testRule = require('./test-rule');
2
-
3
-
4
- /**
5
- * @typedef { import('./types.js').ModdleElement } ModdleElement
6
- *
7
- * @typedef { import('./types.js').ReportingCategory } ReportingCategory
8
- * @typedef { import('./types.js').ReportingCategoryMap } ReportingCategoryMap
9
- * @typedef { import('./types.js').RuleErrorCategory } RuleErrorCategory
10
- *
11
- * @typedef { import('./types.js').Resolver } Resolver
12
- * @typedef { import('./types.js').Config } Config
13
- *
14
- * @typedef { import('./types.js').RuleFactory } RuleFactory
15
- *
16
- * @typedef { import('./types.js').RuleDefinition } RuleDefinition
17
- * @typedef { import('./types.js').RuleConfigs } RuleConfigs
18
- * @typedef { import('./types.js').RuleConfig } RuleConfig
19
- *
20
- * @typedef { {
21
- * name: string,
22
- * rule: RuleDefinition,
23
- * config: any,
24
- * category: ReportingCategory
25
- * } } ResolvedRuleDefinition
26
- *
27
- * @typedef { import('./types.js').Report } Report
28
- * @typedef { Report & Pick<RuleDefinition, 'meta'> & {
29
- * category: ReportingCategory | RuleErrorCategory
30
- * } } AnnotatedReport
31
- *
32
- * @typedef { import('./types.js').TransformRuleFn } TransformRuleFn
33
- *
34
- * @typedef { Record<string, AnnotatedReport[]> } LintResults
35
- */
36
-
37
-
38
- /**
39
- * Transform a rule after it is loaded.
40
- *
41
- * @param { RuleDefinition } rule
42
- * @param { { pkg: string, ruleName: string } } options
43
- */
44
- const noopTransformRule = (rule, options) => {
45
- return rule;
46
- };
47
-
48
-
49
- /**
50
- * @typeof { ReportingCategoryMap }
51
- */
52
- const categoryMap = {
53
- 0: 'off',
54
- 1: 'warn',
55
- 2: 'error',
56
- 3: 'info'
57
- };
58
-
59
- /**
60
- * @type { RuleErrorCategory }
61
- */
62
- const ruleErrorCategory = 'rule-error';
63
-
64
- /**
65
- * @param { {
66
- * config?: Config,
67
- * resolver: Resolver,
68
- * transformRule?: TransformRuleFn
69
- * } } options
70
- */
71
- function Linter(options) {
72
-
73
- const {
74
- config = {},
75
- resolver,
76
- transformRule = noopTransformRule
77
- } = options || {};
78
-
79
- if (typeof resolver === 'undefined') {
80
- throw new Error('must provide <options.resolver>');
81
- }
82
-
83
- this.config = config;
84
- this.resolver = resolver;
85
-
86
- this.transformRule = transformRule;
87
-
88
- /**
89
- * @type { Record<string, RuleDefinition> }
90
- */
91
- this.cachedRules = {};
92
-
93
- /**
94
- * @type { Record<string, Config> }
95
- */
96
- this.cachedConfigs = {};
97
- }
98
-
99
-
100
- module.exports = Linter;
101
-
102
- /**
103
- * Applies a rule on the moddleRoot and adds reports to the finalReport
104
- *
105
- * @param { ModdleElement } moddleRoot
106
- *
107
- * @param { ResolvedRuleDefinition } ruleDefinition
108
- *
109
- * @return { AnnotatedReport[] } reports
110
- */
111
- Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
112
-
113
- const {
114
- config,
115
- rule,
116
- category,
117
- name
118
- } = ruleDefinition;
119
-
120
- try {
121
-
122
- const reports = testRule({
123
- moddleRoot,
124
- rule,
125
- config
126
- });
127
-
128
- return reports.map(function(report) {
129
- return {
130
- ...report,
131
- meta: rule.meta,
132
- category
133
- };
134
- });
135
- } catch (e) {
136
- console.error('rule <' + name + '> failed with error: ', e);
137
-
138
- return [
139
- {
140
- message: /** @type { Error } */ (e).message,
141
- category: ruleErrorCategory
142
- }
143
- ];
144
- }
145
-
146
- };
147
-
148
-
149
- /**
150
- * @param { string } name
151
- * @param { RuleConfig } config
152
- *
153
- * @return { Promise<RuleDefinition> }
154
- */
155
- Linter.prototype.resolveRule = function(name, config) {
156
-
157
- const {
158
- pkg,
159
- ruleName
160
- } = this.parseRuleName(name);
161
-
162
- const id = `${pkg}-${ruleName}`;
163
-
164
- const rule = this.cachedRules[id];
165
-
166
- if (rule) {
167
- return Promise.resolve(rule);
168
- }
169
-
170
- return Promise.resolve(this.resolver.resolveRule(pkg, ruleName)).then((ruleFactory) => {
171
-
172
- if (!ruleFactory) {
173
- throw new Error(`unknown rule <${name}>`);
174
- }
175
-
176
- const rule = this.cachedRules[id] = this.transformRule(ruleFactory(config), { pkg, ruleName });
177
-
178
- return rule;
179
- });
180
- };
181
-
182
- /**
183
- * @param {string} name
184
- *
185
- * @return {Promise<Config>}
186
- */
187
- Linter.prototype.resolveConfig = function(name) {
188
-
189
- const {
190
- pkg,
191
- configName
192
- } = this.parseConfigName(name);
193
-
194
- const id = `${pkg}-${configName}`;
195
-
196
- const config = this.cachedConfigs[id];
197
-
198
- if (config) {
199
- return Promise.resolve(config);
200
- }
201
-
202
- return Promise.resolve(this.resolver.resolveConfig(pkg, configName)).then((config) => {
203
-
204
- if (!config) {
205
- throw new Error(`unknown config <${name}>`);
206
- }
207
-
208
- const actualConfig = this.cachedConfigs[id] = this.normalizeConfig(config, pkg);
209
-
210
- return actualConfig;
211
- });
212
- };
213
-
214
- /**
215
- * Take a linter config and return list of resolved rules.
216
- *
217
- * @param { Config } config
218
- *
219
- * @return { Promise<ResolvedRuleDefinition[]> }
220
- */
221
- Linter.prototype.resolveRules = function(config) {
222
-
223
- return this.resolveConfiguredRules(config).then((rulesConfig) => {
224
-
225
- // parse rule values
226
- const parsedRules = Object.entries(rulesConfig).map(([ name, value ]) => {
227
- const {
228
- category,
229
- config
230
- } = this.parseRuleValue(value);
231
-
232
- return {
233
- name,
234
- category,
235
- config
236
- };
237
- });
238
-
239
- // filter only for enabled rules
240
- const enabledRules = parsedRules.filter(definition => definition.category !== 'off');
241
-
242
- // load enabled rules
243
- const loaders = enabledRules.map((definition) => {
244
-
245
- const {
246
- name,
247
- config
248
- } = definition;
249
-
250
- return this.resolveRule(name, config).then(function(rule) {
251
- return {
252
- ...definition,
253
- rule
254
- };
255
- });
256
- });
257
-
258
- return Promise.all(loaders);
259
- });
260
- };
261
-
262
- /**
263
- * @param { Config } config
264
- *
265
- * @return { Promise<RuleConfigs> }
266
- */
267
- Linter.prototype.resolveConfiguredRules = function(config) {
268
-
269
- let parents = config.extends;
270
-
271
- if (typeof parents === 'string') {
272
- parents = [ parents ];
273
- }
274
-
275
- if (typeof parents === 'undefined') {
276
- parents = [];
277
- }
278
-
279
- return Promise.all(
280
- parents.map((configName) => {
281
- return this.resolveConfig(configName).then((config) => {
282
- return this.resolveConfiguredRules(config);
283
- });
284
- })
285
- ).then((inheritedRules) => {
286
-
287
- const overrideRules = this.normalizeConfig(config, 'bpmnlint').rules;
288
-
289
- const rules = [ ...inheritedRules, overrideRules ].reduce((rules, currentRules) => {
290
- return {
291
- ...rules,
292
- ...currentRules
293
- };
294
- }, {});
295
-
296
- return rules;
297
- });
298
- };
299
-
300
-
301
- /**
302
- * Lint the given model root, using the specified linter config.
303
- *
304
- * @param { ModdleElement } moddleRoot
305
- * @param { Config } [config] the bpmnlint configuration to use
306
- *
307
- * @return { Promise<LintResults> } lint results, keyed by rule names
308
- */
309
- Linter.prototype.lint = function(moddleRoot, config) {
310
-
311
- config = config || this.config;
312
-
313
- // load rules
314
- return this.resolveRules(config).then((ruleDefinitions) => {
315
-
316
- /**
317
- * @type { LintResults }
318
- */
319
- const allReports = {};
320
-
321
- ruleDefinitions.forEach((ruleDefinition) => {
322
-
323
- const {
324
- name
325
- } = ruleDefinition;
326
-
327
- const reports = this.applyRule(moddleRoot, ruleDefinition);
328
-
329
- if (reports.length) {
330
- allReports[name] = reports;
331
- }
332
- });
333
-
334
- return allReports;
335
- });
336
- };
337
-
338
- /**
339
- * @param { import('./types.js').RuleConfig } value
340
- *
341
- * @return { { config: any, category: ReportingCategory } }
342
- */
343
- Linter.prototype.parseRuleValue = function(value) {
344
-
345
- let category;
346
- let config;
347
-
348
- if (Array.isArray(value)) {
349
- category = value[0];
350
- config = value[1];
351
- } else {
352
- category = value;
353
- config = {};
354
- }
355
-
356
- // normalize rule flag to <error> and <warn> which
357
- // may be upper case or a number at this point
358
- if (typeof category === 'string') {
359
- category = category.toLowerCase();
360
- }
361
-
362
- category = categoryMap[category] || category;
363
-
364
- return {
365
- config,
366
- category
367
- };
368
- };
369
-
370
- Linter.prototype.parseRuleName = function(name, localPackage = 'bpmnlint') {
371
-
372
- /**
373
- * We recognize the following rule name patterns:
374
- *
375
- * {RULE_NAME} => PKG = 'bpmnlint'
376
- * bpmnlint/{RULE_NAME} => PKG = 'bpmnlint'
377
- * {PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
378
- * bpmnlint-plugin-{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
379
- * @scope/{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
380
- * @scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
381
- */
382
-
383
- const match = /^(?:(?:(@[^/]+)\/)?([^@]{1}[^/]*)\/)?([^/]+)$/.exec(name);
384
-
385
- if (!match) {
386
- throw new Error(`unparseable rule name <${name}>`);
387
- }
388
-
389
- const [
390
- _,
391
- ns,
392
- packageName,
393
- ruleName
394
- ] = match;
395
-
396
- if (!packageName) {
397
- return {
398
- pkg: localPackage,
399
- ruleName
400
- };
401
- }
402
-
403
- const pkg = `${ns ? ns + '/' : '' }${prefixPackage(packageName)}`;
404
-
405
- return {
406
- pkg,
407
- ruleName
408
- };
409
- };
410
-
411
-
412
- Linter.prototype.parseConfigName = function(name) {
413
-
414
- /**
415
- * We recognize the following config name patterns:
416
- *
417
- * bpmnlint:{CONFIG_NAME} => PKG = 'bpmnlint'
418
- * plugin:{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
419
- * plugin:bpmnlint-plugin-{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
420
- * plugin:@scope/{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
421
- * plugin:@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
422
- */
423
-
424
- const match = /^(?:(?:plugin:(?:(@[^/]+)\/)?([^@]{1}[^/]*)\/)|bpmnlint:)([^/]+)$/.exec(name);
425
-
426
- if (!match) {
427
- throw new Error(`unparseable config name <${name}>`);
428
- }
429
-
430
- const [
431
- _,
432
- ns,
433
- packageName,
434
- configName
435
- ] = match;
436
-
437
- if (!packageName) {
438
- return {
439
- pkg: 'bpmnlint',
440
- configName
441
- };
442
- }
443
-
444
- const pkg = `${ns ? ns + '/' : '' }${prefixPackage(packageName)}`;
445
-
446
- return {
447
- pkg,
448
- configName
449
- };
450
- };
451
-
452
-
453
- Linter.prototype.getSimplePackageName = function(name) {
454
-
455
- /**
456
- * We recognize the following package name patterns:
457
- *
458
- * bpmnlint => PKG = 'bpmnlint'
459
- * {PACKAGE_SHORTCUT} => PKG = PACKAGE_SHORTCUT
460
- * bpmnlint-plugin-{PACKAGE_SHORTCUT}' => PKG = PACKAGE_SHORTCUT
461
- * @scope/{PACKAGE_SHORTCUT} => PKG = '@scope/{PACKAGE_SHORTCUT}'
462
- * @scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}' => PKG = '@scope/PACKAGE_SHORTCUT'
463
- */
464
-
465
- const match = /^(?:(@[^/]+)\/)?([^/]+)$/.exec(name);
466
-
467
- if (!match) {
468
- throw new Error(`unparseable package name <${name}>`);
469
- }
470
-
471
- const [
472
- _,
473
- ns,
474
- packageName
475
- ] = match;
476
-
477
- return `${ns ? ns + '/' : '' }${unprefixPackage(packageName)}`;
478
- };
479
-
480
-
481
- /**
482
- * Validate and return validated config.
483
- *
484
- * @param {Object} config
485
- * @param {String} localPackage
486
- *
487
- * @return {Object} validated config
488
- */
489
- Linter.prototype.normalizeConfig = function(config, localPackage) {
490
-
491
- const rules = config.rules || {};
492
-
493
- const validatedRules = Object.keys(rules).reduce((normalizedRules, name) => {
494
-
495
- const value = rules[name];
496
-
497
- const {
498
- pkg,
499
- ruleName
500
- } = this.parseRuleName(name, localPackage);
501
-
502
- const normalizedName = (
503
- pkg === 'bpmnlint'
504
- ? ruleName
505
- : `${this.getSimplePackageName(pkg)}/${ruleName}`
506
- );
507
-
508
- normalizedRules[normalizedName] = value;
509
-
510
- return normalizedRules;
511
- }, {});
512
-
513
- return {
514
- ...config,
515
- rules: validatedRules
516
- };
517
- };
518
-
519
-
520
- // helpers ///////////////////////////
521
-
522
- function prefixPackage(pkg) {
523
-
524
- if (pkg === 'bpmnlint') {
525
- return 'bpmnlint';
526
- }
527
-
528
- if (pkg.startsWith('bpmnlint-plugin-')) {
529
- return pkg;
530
- }
531
-
532
- return `bpmnlint-plugin-${pkg}`;
533
- }
534
-
535
-
536
- function unprefixPackage(pkg) {
537
-
538
- if (pkg.startsWith('bpmnlint-plugin-')) {
539
- return pkg.substring('bpmnlint-plugin-'.length);
540
- }
541
-
542
- return pkg;
1
+ const testRule = require('./test-rule');
2
+
3
+
4
+ /**
5
+ * @typedef { import('./types.js').ModdleElement } ModdleElement
6
+ *
7
+ * @typedef { import('./types.js').ReportingCategory } ReportingCategory
8
+ * @typedef { import('./types.js').ReportingCategoryMap } ReportingCategoryMap
9
+ * @typedef { import('./types.js').RuleErrorCategory } RuleErrorCategory
10
+ *
11
+ * @typedef { import('./types.js').Resolver } Resolver
12
+ * @typedef { import('./types.js').Config } Config
13
+ *
14
+ * @typedef { import('./types.js').RuleFactory } RuleFactory
15
+ *
16
+ * @typedef { import('./types.js').RuleDefinition } RuleDefinition
17
+ * @typedef { import('./types.js').RuleConfigs } RuleConfigs
18
+ * @typedef { import('./types.js').RuleConfig } RuleConfig
19
+ *
20
+ * @typedef { {
21
+ * name: string,
22
+ * rule: RuleDefinition,
23
+ * config: any,
24
+ * category: ReportingCategory
25
+ * } } ResolvedRuleDefinition
26
+ *
27
+ * @typedef { import('./types.js').Report } Report
28
+ * @typedef { Report & Pick<RuleDefinition, 'meta'> & {
29
+ * category: ReportingCategory | RuleErrorCategory
30
+ * } } AnnotatedReport
31
+ *
32
+ * @typedef { import('./types.js').TransformRuleFn } TransformRuleFn
33
+ *
34
+ * @typedef { Record<string, AnnotatedReport[]> } LintResults
35
+ */
36
+
37
+
38
+ /**
39
+ * Transform a rule after it is loaded.
40
+ *
41
+ * @param { RuleDefinition } rule
42
+ * @param { { pkg: string, ruleName: string } } options
43
+ */
44
+ const noopTransformRule = (rule, options) => {
45
+ return rule;
46
+ };
47
+
48
+
49
+ /**
50
+ * @typeof { ReportingCategoryMap }
51
+ */
52
+ const categoryMap = {
53
+ 0: 'off',
54
+ 1: 'warn',
55
+ 2: 'error',
56
+ 3: 'info'
57
+ };
58
+
59
+ /**
60
+ * @type { RuleErrorCategory }
61
+ */
62
+ const ruleErrorCategory = 'rule-error';
63
+
64
+ /**
65
+ * @param { {
66
+ * config?: Config,
67
+ * resolver: Resolver,
68
+ * transformRule?: TransformRuleFn
69
+ * } } options
70
+ */
71
+ function Linter(options) {
72
+
73
+ const {
74
+ config = {},
75
+ resolver,
76
+ transformRule = noopTransformRule
77
+ } = options || {};
78
+
79
+ if (typeof resolver === 'undefined') {
80
+ throw new Error('must provide <options.resolver>');
81
+ }
82
+
83
+ this.config = config;
84
+ this.resolver = resolver;
85
+
86
+ this.transformRule = transformRule;
87
+
88
+ /**
89
+ * @type { Record<string, RuleDefinition> }
90
+ */
91
+ this.cachedRules = {};
92
+
93
+ /**
94
+ * @type { Record<string, Config> }
95
+ */
96
+ this.cachedConfigs = {};
97
+ }
98
+
99
+
100
+ module.exports = Linter;
101
+
102
+ /**
103
+ * Applies a rule on the moddleRoot and adds reports to the finalReport
104
+ *
105
+ * @param { ModdleElement } moddleRoot
106
+ *
107
+ * @param { ResolvedRuleDefinition } ruleDefinition
108
+ *
109
+ * @return { AnnotatedReport[] } reports
110
+ */
111
+ Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
112
+
113
+ const {
114
+ config,
115
+ rule,
116
+ category,
117
+ name
118
+ } = ruleDefinition;
119
+
120
+ try {
121
+
122
+ const reports = testRule({
123
+ moddleRoot,
124
+ rule,
125
+ config
126
+ });
127
+
128
+ return reports.map(function(report) {
129
+ return {
130
+ ...report,
131
+ meta: rule.meta,
132
+ category
133
+ };
134
+ });
135
+ } catch (e) {
136
+ console.error('rule <' + name + '> failed with error: ', e);
137
+
138
+ return [
139
+ {
140
+ message: /** @type { Error } */ (e).message,
141
+ category: ruleErrorCategory
142
+ }
143
+ ];
144
+ }
145
+
146
+ };
147
+
148
+
149
+ /**
150
+ * @param { string } name
151
+ * @param { RuleConfig } config
152
+ *
153
+ * @return { Promise<RuleDefinition> }
154
+ */
155
+ Linter.prototype.resolveRule = function(name, config) {
156
+
157
+ const {
158
+ pkg,
159
+ ruleName
160
+ } = this.parseRuleName(name);
161
+
162
+ const id = `${pkg}-${ruleName}`;
163
+
164
+ const rule = this.cachedRules[id];
165
+
166
+ if (rule) {
167
+ return Promise.resolve(rule);
168
+ }
169
+
170
+ return Promise.resolve(this.resolver.resolveRule(pkg, ruleName)).then((ruleFactory) => {
171
+
172
+ if (!ruleFactory) {
173
+ throw new Error(`unknown rule <${name}>`);
174
+ }
175
+
176
+ const rule = this.cachedRules[id] = this.transformRule(ruleFactory(config), { pkg, ruleName });
177
+
178
+ return rule;
179
+ });
180
+ };
181
+
182
+ /**
183
+ * @param {string} name
184
+ *
185
+ * @return {Promise<Config>}
186
+ */
187
+ Linter.prototype.resolveConfig = function(name) {
188
+
189
+ const {
190
+ pkg,
191
+ configName
192
+ } = this.parseConfigName(name);
193
+
194
+ const id = `${pkg}-${configName}`;
195
+
196
+ const config = this.cachedConfigs[id];
197
+
198
+ if (config) {
199
+ return Promise.resolve(config);
200
+ }
201
+
202
+ return Promise.resolve(this.resolver.resolveConfig(pkg, configName)).then((config) => {
203
+
204
+ if (!config) {
205
+ throw new Error(`unknown config <${name}>`);
206
+ }
207
+
208
+ const actualConfig = this.cachedConfigs[id] = this.normalizeConfig(config, pkg);
209
+
210
+ return actualConfig;
211
+ });
212
+ };
213
+
214
+ /**
215
+ * Take a linter config and return list of resolved rules.
216
+ *
217
+ * @param { Config } config
218
+ *
219
+ * @return { Promise<ResolvedRuleDefinition[]> }
220
+ */
221
+ Linter.prototype.resolveRules = function(config) {
222
+
223
+ return this.resolveConfiguredRules(config).then((rulesConfig) => {
224
+
225
+ // parse rule values
226
+ const parsedRules = Object.entries(rulesConfig).map(([ name, value ]) => {
227
+ const {
228
+ category,
229
+ config
230
+ } = this.parseRuleValue(value);
231
+
232
+ return {
233
+ name,
234
+ category,
235
+ config
236
+ };
237
+ });
238
+
239
+ // filter only for enabled rules
240
+ const enabledRules = parsedRules.filter(definition => definition.category !== 'off');
241
+
242
+ // load enabled rules
243
+ const loaders = enabledRules.map((definition) => {
244
+
245
+ const {
246
+ name,
247
+ config
248
+ } = definition;
249
+
250
+ return this.resolveRule(name, config).then(function(rule) {
251
+ return {
252
+ ...definition,
253
+ rule
254
+ };
255
+ });
256
+ });
257
+
258
+ return Promise.all(loaders);
259
+ });
260
+ };
261
+
262
+ /**
263
+ * @param { Config } config
264
+ *
265
+ * @return { Promise<RuleConfigs> }
266
+ */
267
+ Linter.prototype.resolveConfiguredRules = function(config) {
268
+
269
+ let parents = config.extends;
270
+
271
+ if (typeof parents === 'string') {
272
+ parents = [ parents ];
273
+ }
274
+
275
+ if (typeof parents === 'undefined') {
276
+ parents = [];
277
+ }
278
+
279
+ return Promise.all(
280
+ parents.map((configName) => {
281
+ return this.resolveConfig(configName).then((config) => {
282
+ return this.resolveConfiguredRules(config);
283
+ });
284
+ })
285
+ ).then((inheritedRules) => {
286
+
287
+ const overrideRules = this.normalizeConfig(config, 'bpmnlint').rules;
288
+
289
+ const rules = [ ...inheritedRules, overrideRules ].reduce((rules, currentRules) => {
290
+ return {
291
+ ...rules,
292
+ ...currentRules
293
+ };
294
+ }, {});
295
+
296
+ return rules;
297
+ });
298
+ };
299
+
300
+
301
+ /**
302
+ * Lint the given model root, using the specified linter config.
303
+ *
304
+ * @param { ModdleElement } moddleRoot
305
+ * @param { Config } [config] the bpmnlint configuration to use
306
+ *
307
+ * @return { Promise<LintResults> } lint results, keyed by rule names
308
+ */
309
+ Linter.prototype.lint = function(moddleRoot, config) {
310
+
311
+ config = config || this.config;
312
+
313
+ // load rules
314
+ return this.resolveRules(config).then((ruleDefinitions) => {
315
+
316
+ /**
317
+ * @type { LintResults }
318
+ */
319
+ const allReports = {};
320
+
321
+ ruleDefinitions.forEach((ruleDefinition) => {
322
+
323
+ const {
324
+ name
325
+ } = ruleDefinition;
326
+
327
+ const reports = this.applyRule(moddleRoot, ruleDefinition);
328
+
329
+ if (reports.length) {
330
+ allReports[name] = reports;
331
+ }
332
+ });
333
+
334
+ return allReports;
335
+ });
336
+ };
337
+
338
+ /**
339
+ * @param { import('./types.js').RuleConfig } value
340
+ *
341
+ * @return { { config: any, category: ReportingCategory } }
342
+ */
343
+ Linter.prototype.parseRuleValue = function(value) {
344
+
345
+ let category;
346
+ let config;
347
+
348
+ if (Array.isArray(value)) {
349
+ category = value[0];
350
+ config = value[1];
351
+ } else {
352
+ category = value;
353
+ config = {};
354
+ }
355
+
356
+ // normalize rule flag to <error> and <warn> which
357
+ // may be upper case or a number at this point
358
+ if (typeof category === 'string') {
359
+ category = category.toLowerCase();
360
+ }
361
+
362
+ category = categoryMap[category] || category;
363
+
364
+ return {
365
+ config,
366
+ category
367
+ };
368
+ };
369
+
370
+ Linter.prototype.parseRuleName = function(name, localPackage = 'bpmnlint') {
371
+
372
+ /**
373
+ * We recognize the following rule name patterns:
374
+ *
375
+ * {RULE_NAME} => PKG = 'bpmnlint'
376
+ * bpmnlint/{RULE_NAME} => PKG = 'bpmnlint'
377
+ * {PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
378
+ * bpmnlint-plugin-{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
379
+ * @scope/{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
380
+ * @scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}/{RULE_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
381
+ */
382
+
383
+ const match = /^(?:(?:(@[^/]+)\/)?([^@]{1}[^/]*)\/)?([^/]+)$/.exec(name);
384
+
385
+ if (!match) {
386
+ throw new Error(`unparseable rule name <${name}>`);
387
+ }
388
+
389
+ const [
390
+ _,
391
+ ns,
392
+ packageName,
393
+ ruleName
394
+ ] = match;
395
+
396
+ if (!packageName) {
397
+ return {
398
+ pkg: localPackage,
399
+ ruleName
400
+ };
401
+ }
402
+
403
+ const pkg = `${ns ? ns + '/' : '' }${prefixPackage(packageName)}`;
404
+
405
+ return {
406
+ pkg,
407
+ ruleName
408
+ };
409
+ };
410
+
411
+
412
+ Linter.prototype.parseConfigName = function(name) {
413
+
414
+ /**
415
+ * We recognize the following config name patterns:
416
+ *
417
+ * bpmnlint:{CONFIG_NAME} => PKG = 'bpmnlint'
418
+ * plugin:{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
419
+ * plugin:bpmnlint-plugin-{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = 'bpmnlint-plugin-{PACKAGE_SHORTCUT}'
420
+ * plugin:@scope/{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
421
+ * plugin:@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}/{CONFIG_NAME} => PKG = '@scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}'
422
+ */
423
+
424
+ const match = /^(?:(?:plugin:(?:(@[^/]+)\/)?([^@]{1}[^/]*)\/)|bpmnlint:)([^/]+)$/.exec(name);
425
+
426
+ if (!match) {
427
+ throw new Error(`unparseable config name <${name}>`);
428
+ }
429
+
430
+ const [
431
+ _,
432
+ ns,
433
+ packageName,
434
+ configName
435
+ ] = match;
436
+
437
+ if (!packageName) {
438
+ return {
439
+ pkg: 'bpmnlint',
440
+ configName
441
+ };
442
+ }
443
+
444
+ const pkg = `${ns ? ns + '/' : '' }${prefixPackage(packageName)}`;
445
+
446
+ return {
447
+ pkg,
448
+ configName
449
+ };
450
+ };
451
+
452
+
453
+ Linter.prototype.getSimplePackageName = function(name) {
454
+
455
+ /**
456
+ * We recognize the following package name patterns:
457
+ *
458
+ * bpmnlint => PKG = 'bpmnlint'
459
+ * {PACKAGE_SHORTCUT} => PKG = PACKAGE_SHORTCUT
460
+ * bpmnlint-plugin-{PACKAGE_SHORTCUT}' => PKG = PACKAGE_SHORTCUT
461
+ * @scope/{PACKAGE_SHORTCUT} => PKG = '@scope/{PACKAGE_SHORTCUT}'
462
+ * @scope/bpmnlint-plugin-{PACKAGE_SHORTCUT}' => PKG = '@scope/PACKAGE_SHORTCUT'
463
+ */
464
+
465
+ const match = /^(?:(@[^/]+)\/)?([^/]+)$/.exec(name);
466
+
467
+ if (!match) {
468
+ throw new Error(`unparseable package name <${name}>`);
469
+ }
470
+
471
+ const [
472
+ _,
473
+ ns,
474
+ packageName
475
+ ] = match;
476
+
477
+ return `${ns ? ns + '/' : '' }${unprefixPackage(packageName)}`;
478
+ };
479
+
480
+
481
+ /**
482
+ * Validate and return validated config.
483
+ *
484
+ * @param {Object} config
485
+ * @param {String} localPackage
486
+ *
487
+ * @return {Object} validated config
488
+ */
489
+ Linter.prototype.normalizeConfig = function(config, localPackage) {
490
+
491
+ const rules = config.rules || {};
492
+
493
+ const validatedRules = Object.keys(rules).reduce((normalizedRules, name) => {
494
+
495
+ const value = rules[name];
496
+
497
+ const {
498
+ pkg,
499
+ ruleName
500
+ } = this.parseRuleName(name, localPackage);
501
+
502
+ const normalizedName = (
503
+ pkg === 'bpmnlint'
504
+ ? ruleName
505
+ : `${this.getSimplePackageName(pkg)}/${ruleName}`
506
+ );
507
+
508
+ normalizedRules[normalizedName] = value;
509
+
510
+ return normalizedRules;
511
+ }, {});
512
+
513
+ return {
514
+ ...config,
515
+ rules: validatedRules
516
+ };
517
+ };
518
+
519
+
520
+ // helpers ///////////////////////////
521
+
522
+ function prefixPackage(pkg) {
523
+
524
+ if (pkg === 'bpmnlint') {
525
+ return 'bpmnlint';
526
+ }
527
+
528
+ if (pkg.startsWith('bpmnlint-plugin-')) {
529
+ return pkg;
530
+ }
531
+
532
+ return `bpmnlint-plugin-${pkg}`;
533
+ }
534
+
535
+
536
+ function unprefixPackage(pkg) {
537
+
538
+ if (pkg.startsWith('bpmnlint-plugin-')) {
539
+ return pkg.substring('bpmnlint-plugin-'.length);
540
+ }
541
+
542
+ return pkg;
543
543
  }