bpmnlint 8.1.1 → 8.3.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/README.md CHANGED
@@ -37,7 +37,7 @@ Validate your diagrams via the commandline:
37
37
 
38
38
  Our [documentation](https://github.com/bpmn-io/bpmnlint/tree/master/docs/rules#rules) lists all currenty implemented rules, the [`./rules` folder](https://github.com/bpmn-io/bpmnlint/tree/master/rules) contains each rules implementation.
39
39
 
40
- Do you miss a rule that should be included? [Propose a new rule](https://github.com/bpmn-io/bpmnlint/issues/new?template=NEW_RULE.md).
40
+ Do you miss a rule that should be included? [Propose a new rule](https://github.com/bpmn-io/bpmnlint/issues/new?assignees=&labels=rules&template=NEW_RULE.md).
41
41
 
42
42
 
43
43
  ## Configuration
@@ -62,6 +62,66 @@ Add or customize rules using the `rules` block:
62
62
  ```
63
63
 
64
64
 
65
+ ## API
66
+
67
+ Invoke the tool directly from [NodeJS](https://nodejs.org/en):
68
+
69
+ ```javascript
70
+ import Linter from 'bpmnlint';
71
+ import NodeResolver from 'bpmnlint/lib/resolver/node-resolver';
72
+
73
+ import BpmnModdle from 'bpmn-moddle';
74
+
75
+ const linter = new Linter({
76
+ config: {
77
+ extends: 'bpmnlint:recommended'
78
+ },
79
+ resolver: new NodeResolver()
80
+ });
81
+
82
+ const xmlStr = `
83
+ <?xml version="1.0" encoding="UTF-8"?>
84
+ <bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
85
+ id="definitions"
86
+ targetNamespace="http://bpmn.io/schema/bpmn">
87
+ <bpmn:process id="process" />
88
+ </bpmn:definitions>
89
+ `;
90
+
91
+ const {
92
+ rootElement: definitions
93
+ } = await moddle.fromXML(xmlStr);
94
+
95
+ const reports = linter.lint(definitions);
96
+
97
+ // {
98
+ // "end-event-required": [
99
+ // {
100
+ // "id": "process",
101
+ // "message": "Process is missing end event"
102
+ // }
103
+ // ],
104
+ // ...
105
+ // }
106
+ ```
107
+
108
+
109
+ ## Writing a Plug-in
110
+
111
+ Create your first plug-in using the [plugin creator](https://github.com/nikku/create-bpmnlint-plugin):
112
+
113
+ ```sh
114
+ npm init bpmnlint-plugin {PLUGIN_NAME}
115
+ ```
116
+
117
+ Checkout the [bpmnlint-plugin-example](https://github.com/bpmn-io/bpmnlint-plugin-example) for details on how to define, test, and consume custom lint rules. Use the [bpmnlint playground](https://github.com/bpmn-io/bpmnlint-playground) to implement new rules with quick visual feedback.
118
+
119
+
120
+ ## Bundling
121
+
122
+ For browser usage include your [linting configuration](#configuration) using your favorite bundler plug-in ([Rollup](https://www.npmjs.com/package/rollup-plugin-bpmnlint), [Webpack](https://www.npmjs.com/package/bpmnlint-loader)).
123
+
124
+
65
125
  ## Visual Feedback
66
126
 
67
127
  Integrate the linter via [bpmn-js-bpmnlint](https://github.com/bpmn-io/bpmn-js-bpmnlint) into [bpmn-js](https://github.com/bpmn-io/bpmn-js) and get direct feedback during modeling.
@@ -69,13 +129,12 @@ Integrate the linter via [bpmn-js-bpmnlint](https://github.com/bpmn-io/bpmn-js-b
69
129
  To try out visual validation, checkout the [bpmnlint playground](https://github.com/bpmn-io/bpmnlint-playground).
70
130
 
71
131
 
72
- ## Writing / Consuming Custom Rules
73
-
74
- Use the [bpmnlint playground](https://github.com/bpmn-io/bpmnlint-playground) to implement new rules with quick visual feedback.
75
-
76
- Create your first plug-in using the [plugin creator](https://github.com/nikku/create-bpmnlint-plugin).
132
+ ## Related
77
133
 
78
- Checkout the [bpmnlint-plugin-example](https://github.com/bpmn-io/bpmnlint-plugin-example) for details on how to define, test, and consume custom lint rules.
134
+ * [bpmnlint-plugin-example](https://github.com/bpmn-io/bpmnlint-plugin-example) - an example plug-in
135
+ * [bpmnlint-playground](https://github.com/bpmn-io/bpmnlint-playground) - a playground to implement new rules with quick visual feedback
136
+ * [bpmnlint-generate-docs-images](https://github.com/bpmn-io/bpmnlint-generate-docs-images) - Generate images for your bpmnlint documentation
137
+ * [bpmn-js-bpmnlint](https://github.com/bpmn-io/bpmn-js-bpmnlint) - integration into [bpmn-js](https://github.com/bpmn-io/bpmn-js)
79
138
 
80
139
 
81
140
  ## License
package/config/all.js CHANGED
@@ -10,6 +10,8 @@ const allRules = [
10
10
  'no-duplicate-sequence-flows',
11
11
  'no-gateway-join-fork',
12
12
  'no-implicit-split',
13
+ 'no-implicit-end',
14
+ 'no-implicit-start',
13
15
  'no-inclusive-gateway',
14
16
  'single-blank-start-event',
15
17
  'single-event-definition',
@@ -11,6 +11,8 @@ module.exports = {
11
11
  'no-duplicate-sequence-flows': 'error',
12
12
  'no-gateway-join-fork': 'error',
13
13
  'no-implicit-split': 'error',
14
+ 'no-implicit-end': 'error',
15
+ 'no-implicit-start': 'error',
14
16
  'no-inclusive-gateway': 'error',
15
17
  'single-blank-start-event': 'error',
16
18
  'single-event-definition': 'error',
package/lib/linter.js CHANGED
@@ -3,7 +3,8 @@ const testRule = require('./test-rule');
3
3
  const categoryMap = {
4
4
  0: 'off',
5
5
  1: 'warn',
6
- 2: 'error'
6
+ 2: 'error',
7
+ 3: 'info'
7
8
  };
8
9
 
9
10
 
@@ -1,6 +1,8 @@
1
1
  const Module = require('module');
2
2
  const path = require('path');
3
3
 
4
+ const { isString } = require('min-dash');
5
+
4
6
  /**
5
7
  * A resolver that locates rules and configurations
6
8
  * using node module resolution.
@@ -26,15 +28,48 @@ NodeResolver.prototype.resolveRule = function(pkg, ruleName) {
26
28
 
27
29
  pkg = this.normalizePkg(pkg);
28
30
 
31
+ let pkgInstance;
32
+
33
+ // (1) try resolving rule via $PKG.rules[$NAME]
34
+ try {
35
+ pkgInstance = this.require(pkg);
36
+ } catch (err) {
37
+
38
+ /* ignore */
39
+ }
40
+
41
+ if (pkgInstance) {
42
+ const rules = pkgInstance.rules || {};
43
+
44
+ if (ruleName in rules) {
45
+ const rule = rules[ruleName];
46
+
47
+ if (!isString(rule)) {
48
+ throw new Error('cannot resolve rule <' + ruleName + '> from <' + originalPkg + '>: illegal rule export (expected path reference)');
49
+ }
50
+
51
+ // local reference, resolved relative to pkg location
52
+ if (rule.startsWith('.')) {
53
+ const pkgDir = path.dirname(this.require.resolve(pkg));
54
+
55
+ return this.require(path.posix.normalize(`${ pkgDir }/${ rule }`));
56
+ }
57
+
58
+ // absolute reference
59
+ return this.require(rule);
60
+ }
61
+ }
62
+
63
+ // (2) try resolving rule via $PKG/rules/$NAME
29
64
  try {
30
65
  if (pkg === 'bpmnlint') {
31
66
  return this.requireLocal(`../../rules/${ruleName}`);
32
67
  } else {
33
68
  return this.require(`${pkg}/rules/${ruleName}`);
34
69
  }
35
- } catch (err) {
36
- throw new Error('Cannot resolve rule <' + ruleName + '> from <' + originalPkg + '>');
37
- }
70
+ } catch (err) { /* ignore */ }
71
+
72
+ throw new Error('cannot resolve rule <' + ruleName + '> from <' + originalPkg + '>');
38
73
  };
39
74
 
40
75
  NodeResolver.prototype.resolveConfig = function(pkg, configName) {
@@ -43,16 +78,7 @@ NodeResolver.prototype.resolveConfig = function(pkg, configName) {
43
78
 
44
79
  pkg = this.normalizePkg(pkg);
45
80
 
46
- // resolve config via $PKG/config/$NAME
47
- try {
48
- if (pkg === 'bpmnlint') {
49
- return this.requireLocal(`../../config/${configName}`);
50
- } else {
51
- return this.require(`${pkg}/config/${configName}`);
52
- }
53
- } catch (err) { /* ignore */ }
54
-
55
- // resolve config via $PKG.configs[$NAME]
81
+ // (1) try resolving config via $PKG.configs[$NAME]
56
82
  try {
57
83
  const instance = this.require(pkg);
58
84
 
@@ -66,8 +92,17 @@ NodeResolver.prototype.resolveConfig = function(pkg, configName) {
66
92
  /* ignore */
67
93
  }
68
94
 
95
+ // (2) try resolving config via $PKG/config/$NAME
96
+ try {
97
+ if (pkg === 'bpmnlint') {
98
+ return this.requireLocal(`../../config/${configName}`);
99
+ } else {
100
+ return this.require(`${pkg}/config/${configName}`);
101
+ }
102
+ } catch (err) { /* ignore */ }
103
+
69
104
  throw new Error(
70
- 'Cannot resolve config <' + configName + '> from <' + originalPkg + '>'
105
+ 'cannot resolve config <' + configName + '> from <' + originalPkg + '>'
71
106
  );
72
107
  };
73
108
 
@@ -1,3 +1,7 @@
1
+ const path = require('path').posix;
2
+
3
+ const { isString } = require('min-dash');
4
+
1
5
  const Linter = require('../linter');
2
6
 
3
7
  const NodeResolver = require('../resolver/node-resolver');
@@ -53,7 +57,7 @@ Resolver.prototype.resolveRule = function(pkg, ruleName) {
53
57
  const rule = cache[pkg + '/' + ruleName];
54
58
 
55
59
  if (!rule) {
56
- throw new Error('cannot resolve rule <' + pkg + '/' + ruleName + '>');
60
+ throw new Error('cannot resolve rule <' + pkg + '/' + ruleName + '>: not bundled');
57
61
  }
58
62
 
59
63
  return rule;
@@ -61,7 +65,7 @@ Resolver.prototype.resolveRule = function(pkg, ruleName) {
61
65
 
62
66
  Resolver.prototype.resolveConfig = function(pkg, configName) {
63
67
  throw new Error(
64
- 'cannot resolve config <' + configName + '> in <' + pkg +'>'
68
+ 'cannot resolve config <' + configName + '> in <' + pkg +'>: not bundled'
65
69
  );
66
70
  };
67
71
 
@@ -93,12 +97,68 @@ export default bundle;
93
97
  pkg, ruleName
94
98
  } = linter.parseRuleName(key);
95
99
 
96
- return `
97
- import rule_${idx} from '${resolver.normalizePkg(pkg)}/rules/${ruleName}';
98
- cache['${pkg}/${ruleName}'] = rule_${idx};`;
100
+ return createImportStatement(pkg, ruleName, idx, resolver);
99
101
  }).filter(e => e).join('\n');
100
102
 
101
- return `${preambleCode}\n\n${importCode}`;
103
+ return `${preambleCode}${importCode}`;
102
104
  }
103
105
 
104
- module.exports = compileConfig;
106
+ module.exports = compileConfig;
107
+
108
+ function requirePkg(resolver, pkg) {
109
+ try {
110
+ return resolver.require(pkg);
111
+ } catch (err) {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ function createImportStatement(pkg, ruleName, idx, resolver) {
117
+ const originalPkg = pkg;
118
+
119
+ pkg = resolver.normalizePkg(pkg);
120
+
121
+ const pkgExport = requirePkg(resolver, pkg);
122
+
123
+ // (1) try resolving rule via $PKG.rules[$NAME]
124
+ if (pkgExport && pkgExport.rules) {
125
+ const rules = pkgExport.rules;
126
+
127
+ if (ruleName in rules) {
128
+ const rule = rules[ruleName];
129
+
130
+ if (!isString(rule)) {
131
+ throw new Error(
132
+ `failed to bundle rule <${ruleName}> from <${ pkg }>: illegal rule export (expected path reference)`
133
+ );
134
+ }
135
+
136
+ const rulePath = computeRuleImport(resolver, pkg, rule);
137
+
138
+ return `
139
+ import rule_${ idx } from '${rulePath}';
140
+
141
+ cache['${ originalPkg }/${ ruleName }'] = rule_${ idx };`;
142
+
143
+ }
144
+ }
145
+
146
+ // (2) resolve rule via $PKG/rules/$NAME
147
+ return `
148
+ import rule_${ idx } from '${ pkg }/rules/${ ruleName }';
149
+
150
+ cache['${ originalPkg }/${ ruleName }'] = rule_${ idx };`;
151
+ }
152
+
153
+ function computeRuleImport(resolver, pkg, rule) {
154
+
155
+ // local reference, resolved relative to pkg location
156
+ if (rule.startsWith('.')) {
157
+ const pkgDir = path.dirname(resolver.require.resolve(pkg));
158
+
159
+ return path.posix.normalize(`${ pkgDir }/${ rule }`);
160
+ }
161
+
162
+ // absolute reference
163
+ return path.normalize(rule);
164
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bpmnlint",
3
3
  "description": "Validate your BPMN diagrams based on configurable lint rules",
4
- "version": "8.1.1",
4
+ "version": "8.3.0",
5
5
  "main": "lib/index.js",
6
6
  "keywords": [
7
7
  "bpmnlint",
@@ -30,8 +30,9 @@
30
30
  "all": "run-s lint test-coverage",
31
31
  "lint": "eslint .",
32
32
  "dev": "npm test -- --watch",
33
- "test": "mocha -r esm --exclude 'test/integration/cli/**' 'test/**/*.js'",
34
- "test-coverage": "nyc --reporter=lcov --reporter=html npm test"
33
+ "test": "mocha -r esm --exclude 'test/integration/*/**' 'test/**/*.js'",
34
+ "test-coverage": "nyc --reporter=lcov --reporter=html npm test",
35
+ "generate-docs-images": "npx @bpmn-io/bpmnlint-generate-docs-images ."
35
36
  },
36
37
  "bin": {
37
38
  "bpmnlint": "./bin/bpmnlint.js"
@@ -39,19 +40,20 @@
39
40
  "dependencies": {
40
41
  "@bpmn-io/moddle-utils": "^0.1.0",
41
42
  "ansi-colors": "^4.1.3",
42
- "bpmn-moddle": "^7.1.3",
43
- "bpmnlint-utils": "^1.0.2",
43
+ "bpmn-moddle": "^8.0.1",
44
+ "bpmnlint-utils": "^1.1.1",
44
45
  "cli-table": "^0.3.11",
45
46
  "color-support": "^1.1.3",
46
- "min-dash": "^3.8.1",
47
+ "min-dash": "^4.1.1",
47
48
  "mri": "^1.2.0",
48
49
  "pluralize": "^7.0.0",
49
50
  "tiny-glob": "^0.2.9"
50
51
  },
51
52
  "devDependencies": {
53
+ "@bpmn-io/bpmnlint-generate-docs-images": "^0.3.2",
52
54
  "chai": "^4.3.4",
53
- "eslint": "^8.23.1",
54
- "eslint-plugin-bpmn-io": "^0.16.0",
55
+ "eslint": "^8.42.0",
56
+ "eslint-plugin-bpmn-io": "^1.0.0",
55
57
  "esm": "^3.2.25",
56
58
  "execa": "^5.1.1",
57
59
  "install-local": "^3.0.1",
@@ -0,0 +1,30 @@
1
+ const {
2
+ is,
3
+ isAny
4
+ } = require('bpmnlint-utils');
5
+
6
+
7
+ /**
8
+ * A rule that checks that an element is not an implicit end (token sink).
9
+ */
10
+ module.exports = function() {
11
+
12
+ function isImplicitEnd(node) {
13
+ const outgoing = node.outgoing || [];
14
+
15
+ return !is(node, 'bpmn:EndEvent') && outgoing.length === 0;
16
+ }
17
+
18
+ function check(node, reporter) {
19
+
20
+ if (!isAny(node, [ 'bpmn:Event', 'bpmn:Activity', 'bpmn:Gateway' ])) {
21
+ return;
22
+ }
23
+
24
+ if (isImplicitEnd(node)) {
25
+ reporter.report(node.id, 'Element is an implicit end');
26
+ }
27
+ }
28
+
29
+ return { check };
30
+ };
@@ -0,0 +1,30 @@
1
+ const {
2
+ is,
3
+ isAny
4
+ } = require('bpmnlint-utils');
5
+
6
+
7
+ /**
8
+ * A rule that checks that an element is not an implicit start (token spawn).
9
+ */
10
+ module.exports = function() {
11
+
12
+ function isImplicitStart(node) {
13
+ const incoming = node.incoming || [];
14
+
15
+ return !is(node, 'bpmn:StartEvent') && incoming.length === 0;
16
+ }
17
+
18
+ function check(node, reporter) {
19
+
20
+ if (!isAny(node, [ 'bpmn:Event', 'bpmn:Activity', 'bpmn:Gateway' ])) {
21
+ return;
22
+ }
23
+
24
+ if (isImplicitStart(node)) {
25
+ reporter.report(node.id, 'Element is an implicit start');
26
+ }
27
+ }
28
+
29
+ return { check };
30
+ };