bpmnlint 11.1.0 → 11.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/bin/bpmnlint.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
+
3
+ const { readFile } = require('node:fs/promises');
4
+ const { writeFileSync, existsSync } = require('node:fs');
5
+ const { resolve: resolvePath } = require('node:path');
6
+
2
7
  const mri = require('mri');
3
- const fs = require('fs');
4
- const path = require('path');
5
8
  const colors = require('ansi-colors');
6
9
 
7
10
  colors.enabled = require('color-support').hasBasic;
@@ -14,11 +17,9 @@ const {
14
17
  magenta
15
18
  } = colors;
16
19
 
17
- const { promisify } = require('util');
18
-
19
20
  const tinyGlob = require('tiny-glob');
20
- const readFile = promisify(fs.readFile);
21
21
 
22
+ // @ts-expect-error 'missing <bpmn-moddle> types'
22
23
  const BpmnModdle = require('bpmn-moddle');
23
24
 
24
25
  const Linter = require('../lib/linter');
@@ -30,6 +31,11 @@ const pluralize = require('pluralize');
30
31
 
31
32
  const { pathStringify } = require('@bpmn-io/moddle-utils');
32
33
 
34
+ /**
35
+ * @typedef { import('../lib/types').ModdleElement } ModdleElement
36
+ * @typedef { Error & { element: ModdleElement } } BPMNImportWarning
37
+ */
38
+
33
39
  const CONFIG_NAME = '.bpmnlintrc';
34
40
 
35
41
  const DEFAULT_CONFIG_CONTENTS = `{
@@ -76,7 +82,7 @@ function glob(files) {
76
82
  *
77
83
  * @param {string} diagramXML
78
84
  *
79
- * @return { { rootElement: any; warnings: Error[], error?: Error } } parseResult
85
+ * @return { Promise<{ moddleElement: any; warnings: Error[], error?: Error }> } parseResult
80
86
  */
81
87
  async function parseDiagram(diagramXML) {
82
88
 
@@ -93,10 +99,14 @@ async function parseDiagram(diagramXML) {
93
99
  } catch (error) {
94
100
 
95
101
  const {
102
+
103
+ // @ts-expect-error
96
104
  warnings = []
97
105
  } = error;
98
106
 
99
107
  return {
108
+
109
+ // @ts-expect-error
100
110
  error,
101
111
  warnings
102
112
  };
@@ -215,7 +225,7 @@ function printReports(filePath, results) {
215
225
 
216
226
  if (problemCount) {
217
227
  console.log();
218
- console.log(underline(path.resolve(filePath)));
228
+ console.log(underline(resolvePath(filePath)));
219
229
  console.log(table.toString());
220
230
  }
221
231
 
@@ -230,9 +240,9 @@ async function lintDiagram(diagramPath, config) {
230
240
  let diagramXML;
231
241
 
232
242
  try {
233
- diagramXML = await readFile(path.resolve(diagramPath), 'utf-8');
243
+ diagramXML = await readFile(resolvePath(diagramPath), 'utf-8');
234
244
  } catch (error) {
235
- return errorAndExit(`Error: Failed to read ${diagramPath}\n\n%s`, error.message);
245
+ throw errorAndExit(`Error: Failed to read ${diagramPath}\n\n%s`, /** @type { Error } */ (error).message);
236
246
  }
237
247
 
238
248
 
@@ -259,7 +269,7 @@ async function lintDiagram(diagramPath, config) {
259
269
  const {
260
270
  element,
261
271
  message
262
- } = warning;
272
+ } = /** @type { BPMNImportWarning } */ (warning);
263
273
 
264
274
  const id = element && element.id;
265
275
 
@@ -286,7 +296,7 @@ async function lintDiagram(diagramPath, config) {
286
296
 
287
297
  return printReports(diagramPath, allResults);
288
298
  } catch (e) {
289
- return errorAndExit(e);
299
+ throw errorAndExit(e);
290
300
  }
291
301
  }
292
302
 
@@ -306,17 +316,10 @@ async function lint(files, config, maxWarnings) {
306
316
 
307
317
  const problemCount = errorCount + warningCount;
308
318
 
309
- let color;
319
+ if (problemCount) {
310
320
 
311
- if (warningCount) {
312
- color = boldYellow;
313
- }
321
+ const color = warningCount ? boldYellow : boldRed;
314
322
 
315
- if (errorCount) {
316
- color = boldRed;
317
- }
318
-
319
- if (problemCount) {
320
323
  console.log();
321
324
  console.log(color(
322
325
  `✖ ${problemCount} ${pluralize('problem', problemCount)} (${errorCount} ${pluralize('error', errorCount)}, ${warningCount} ${pluralize('warning', warningCount)})`
@@ -357,17 +360,17 @@ async function run() {
357
360
  }
358
361
 
359
362
  if (init) {
360
- if (fs.existsSync(CONFIG_NAME)) {
361
- return errorAndExit('Not overriding existing .bpmnlintrc');
363
+ if (existsSync(CONFIG_NAME)) {
364
+ throw errorAndExit('Not overriding existing .bpmnlintrc');
362
365
  }
363
366
 
364
- fs.writeFileSync(CONFIG_NAME, DEFAULT_CONFIG_CONTENTS, 'utf8');
367
+ writeFileSync(CONFIG_NAME, DEFAULT_CONFIG_CONTENTS, 'utf8');
365
368
 
366
- return infoAndExit(`Created ${magenta(CONFIG_NAME)} file`);
369
+ throw infoAndExit(`Created ${magenta(CONFIG_NAME)} file`);
367
370
  }
368
371
 
369
372
  if (files.length === 0) {
370
- return errorAndExit('Error: bpmn file path missing');
373
+ throw errorAndExit('Error: bpmn file path missing');
371
374
  }
372
375
 
373
376
  const configPath = configOverridePath || CONFIG_NAME;
@@ -388,13 +391,13 @@ async function run() {
388
391
  Learn more about configuring bpmnlint: https://github.com/bpmn-io/bpmnlint#configuration`
389
392
  );
390
393
 
391
- return errorAndExit(message);
394
+ throw errorAndExit(message);
392
395
  }
393
396
 
394
397
  try {
395
398
  config = JSON.parse(configString);
396
- } catch (err) {
397
- return errorAndExit('Error: Could not parse %s\n\n%s', configPath, err.message);
399
+ } catch (error) {
400
+ throw errorAndExit('Error: Could not parse %s\n\n%s', configPath, /** @type { Error } */ (error).message);
398
401
  }
399
402
 
400
403
  const actualFiles = await glob(files);
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ rules: {
3
+ 'ad-hoc-sub-process': 'error',
4
+ 'event-sub-process-typed-start-event': 'error',
5
+ 'link-event': 'error',
6
+ 'no-duplicate-sequence-flows': 'warn',
7
+ 'sub-process-blank-start-event': 'error'
8
+ }
9
+ };
package/lib/linter.js CHANGED
@@ -1,5 +1,54 @@
1
1
  const testRule = require('./test-rule');
2
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
+ */
3
52
  const categoryMap = {
4
53
  0: 'off',
5
54
  1: 'warn',
@@ -7,15 +56,25 @@ const categoryMap = {
7
56
  3: 'info'
8
57
  };
9
58
 
59
+ /**
60
+ * @type { RuleErrorCategory }
61
+ */
10
62
  const ruleErrorCategory = 'rule-error';
11
63
 
12
-
13
- function Linter(options = {}) {
64
+ /**
65
+ * @param { {
66
+ * config?: Config,
67
+ * resolver: Resolver,
68
+ * transformRule?: TransformRuleFn
69
+ * } } options
70
+ */
71
+ function Linter(options) {
14
72
 
15
73
  const {
16
- config,
17
- resolver
18
- } = options;
74
+ config = {},
75
+ resolver,
76
+ transformRule = noopTransformRule
77
+ } = options || {};
19
78
 
20
79
  if (typeof resolver === 'undefined') {
21
80
  throw new Error('must provide <options.resolver>');
@@ -24,7 +83,16 @@ function Linter(options = {}) {
24
83
  this.config = config;
25
84
  this.resolver = resolver;
26
85
 
86
+ this.transformRule = transformRule;
87
+
88
+ /**
89
+ * @type { Record<string, RuleDefinition> }
90
+ */
27
91
  this.cachedRules = {};
92
+
93
+ /**
94
+ * @type { Record<string, Config> }
95
+ */
28
96
  this.cachedConfigs = {};
29
97
  }
30
98
 
@@ -34,14 +102,11 @@ module.exports = Linter;
34
102
  /**
35
103
  * Applies a rule on the moddleRoot and adds reports to the finalReport
36
104
  *
37
- * @param {ModdleElement} moddleRoot
105
+ * @param { ModdleElement } moddleRoot
38
106
  *
39
- * @param {Object} ruleDefinition.name
40
- * @param {Object} ruleDefinition.config
41
- * @param {Object} ruleDefinition.category
42
- * @param {Rule} ruleDefinition.rule
107
+ * @param { ResolvedRuleDefinition } ruleDefinition
43
108
  *
44
- * @return {Array<ValidationErrors>} rule reports
109
+ * @return { AnnotatedReport[] } reports
45
110
  */
46
111
  Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
47
112
 
@@ -63,6 +128,7 @@ Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
63
128
  return reports.map(function(report) {
64
129
  return {
65
130
  ...report,
131
+ meta: rule.meta,
66
132
  category
67
133
  };
68
134
  });
@@ -71,7 +137,7 @@ Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
71
137
 
72
138
  return [
73
139
  {
74
- message: e.message,
140
+ message: /** @type { Error } */ (e).message,
75
141
  category: ruleErrorCategory
76
142
  }
77
143
  ];
@@ -80,6 +146,12 @@ Linter.prototype.applyRule = function applyRule(moddleRoot, ruleDefinition) {
80
146
  };
81
147
 
82
148
 
149
+ /**
150
+ * @param { string } name
151
+ * @param { RuleConfig } config
152
+ *
153
+ * @return { Promise<RuleDefinition> }
154
+ */
83
155
  Linter.prototype.resolveRule = function(name, config) {
84
156
 
85
157
  const {
@@ -101,12 +173,17 @@ Linter.prototype.resolveRule = function(name, config) {
101
173
  throw new Error(`unknown rule <${name}>`);
102
174
  }
103
175
 
104
- const rule = this.cachedRules[id] = ruleFactory(config);
176
+ const rule = this.cachedRules[id] = this.transformRule(ruleFactory(config), { pkg, ruleName });
105
177
 
106
178
  return rule;
107
179
  });
108
180
  };
109
181
 
182
+ /**
183
+ * @param {string} name
184
+ *
185
+ * @return {Promise<Config>}
186
+ */
110
187
  Linter.prototype.resolveConfig = function(name) {
111
188
 
112
189
  const {
@@ -137,9 +214,9 @@ Linter.prototype.resolveConfig = function(name) {
137
214
  /**
138
215
  * Take a linter config and return list of resolved rules.
139
216
  *
140
- * @param {Object} config
217
+ * @param { Config } config
141
218
  *
142
- * @return {Array<RuleDefinition>}
219
+ * @return { Promise<ResolvedRuleDefinition[]> }
143
220
  */
144
221
  Linter.prototype.resolveRules = function(config) {
145
222
 
@@ -182,7 +259,11 @@ Linter.prototype.resolveRules = function(config) {
182
259
  });
183
260
  };
184
261
 
185
-
262
+ /**
263
+ * @param { Config } config
264
+ *
265
+ * @return { Promise<RuleConfigs> }
266
+ */
186
267
  Linter.prototype.resolveConfiguredRules = function(config) {
187
268
 
188
269
  let parents = config.extends;
@@ -220,10 +301,10 @@ Linter.prototype.resolveConfiguredRules = function(config) {
220
301
  /**
221
302
  * Lint the given model root, using the specified linter config.
222
303
  *
223
- * @param {ModdleElement} moddleRoot
224
- * @param {Object} [config] the bpmnlint configuration to use
304
+ * @param { ModdleElement } moddleRoot
305
+ * @param { Config } [config] the bpmnlint configuration to use
225
306
  *
226
- * @return {Object} lint results, keyed by category names
307
+ * @return { Promise<LintResults> } lint results, keyed by rule names
227
308
  */
228
309
  Linter.prototype.lint = function(moddleRoot, config) {
229
310
 
@@ -232,6 +313,9 @@ Linter.prototype.lint = function(moddleRoot, config) {
232
313
  // load rules
233
314
  return this.resolveRules(config).then((ruleDefinitions) => {
234
315
 
316
+ /**
317
+ * @type { LintResults }
318
+ */
235
319
  const allReports = {};
236
320
 
237
321
  ruleDefinitions.forEach((ruleDefinition) => {
@@ -251,7 +335,11 @@ Linter.prototype.lint = function(moddleRoot, config) {
251
335
  });
252
336
  };
253
337
 
254
-
338
+ /**
339
+ * @param { import('./types.js').RuleConfig } value
340
+ *
341
+ * @return { { config: any, category: ReportingCategory } }
342
+ */
255
343
  Linter.prototype.parseRuleValue = function(value) {
256
344
 
257
345
  let category;
@@ -1,14 +1,23 @@
1
- const Module = require('module');
2
- const path = require('path');
1
+ const Module = require('node:module');
2
+ const path = require('node:path');
3
+ const { cwd } = require('node:process');
3
4
 
4
5
  const { isString } = require('min-dash');
5
6
 
7
+ /**
8
+ * @typedef { import('../types.js').RuleFactory } RuleFactory
9
+ * @typedef { import('../types.js').Config } Config
10
+ */
11
+
12
+
6
13
  /**
7
14
  * A resolver that locates rules and configurations
8
15
  * using node module resolution.
16
+ *
17
+ * @param { { require?: NodeRequire, requireLocal?: NodeRequire } } [options]
9
18
  */
10
19
  function NodeResolver(options) {
11
- this.require = options && options.require || createScopedRequire(process.cwd());
20
+ this.require = options && options.require || createScopedRequire(cwd());
12
21
 
13
22
  this.requireLocal = options && options.requireLocal || require;
14
23
 
@@ -22,6 +31,12 @@ function NodeResolver(options) {
22
31
  module.exports = NodeResolver;
23
32
 
24
33
 
34
+ /**
35
+ * @param { string } pkg
36
+ * @param { string } ruleName
37
+ *
38
+ * @return { RuleFactory }
39
+ */
25
40
  NodeResolver.prototype.resolveRule = function(pkg, ruleName) {
26
41
 
27
42
  const originalPkg = pkg;
@@ -72,6 +87,12 @@ NodeResolver.prototype.resolveRule = function(pkg, ruleName) {
72
87
  throw new Error('cannot resolve rule <' + ruleName + '> from <' + originalPkg + '>');
73
88
  };
74
89
 
90
+ /**
91
+ * @param { string } pkg
92
+ * @param { string } configName
93
+ *
94
+ * @return { Config }
95
+ */
75
96
  NodeResolver.prototype.resolveConfig = function(pkg, configName) {
76
97
 
77
98
  const originalPkg = pkg;
@@ -116,6 +137,9 @@ NodeResolver.prototype.normalizePkg = function(pkg) {
116
137
 
117
138
  // helpers ////////////////////
118
139
 
140
+ /**
141
+ * @param {string} cwd
142
+ */
119
143
  function createScopedRequire(cwd) {
120
144
  return Module.createRequire(path.join(cwd, '__placeholder__.js'));
121
145
  }
@@ -1,16 +1,38 @@
1
+ /**
2
+ * @typedef { import('../types.js').RuleFactory } RuleFactory
3
+ * @typedef { import('../types.js').Config } Config
4
+ */
5
+
6
+
7
+ /**
8
+ * A resolver that resolves rules and packages from a static cache.
9
+ *
10
+ * @param { any } cache
11
+ */
1
12
  function StaticResolver(cache) {
2
13
  this.cache = cache;
3
14
  }
4
15
 
5
16
  module.exports = StaticResolver;
6
17
 
7
-
18
+ /**
19
+ * @param { string } pkg
20
+ * @param { string } ruleName
21
+ *
22
+ * @return { RuleFactory }
23
+ */
8
24
  StaticResolver.prototype.resolveRule = function(pkg, ruleName) {
9
- return this.resolve('rule', pkg, ruleName);
25
+ return /** @type { RuleFactory } */ (this.resolve('rule', pkg, ruleName));
10
26
  };
11
27
 
28
+ /**
29
+ * @param { string } pkg
30
+ * @param { string } configName
31
+ *
32
+ * @return { Config }
33
+ */
12
34
  StaticResolver.prototype.resolveConfig = function(pkg, configName) {
13
- return this.resolve('config', pkg, configName);
35
+ return /** @type { Config } */ (this.resolve('config', pkg, configName));
14
36
  };
15
37
 
16
38
  StaticResolver.prototype.resolve = function(type, pkg, name) {
package/lib/test-rule.js CHANGED
@@ -7,29 +7,49 @@ const {
7
7
  } = require('min-dash');
8
8
 
9
9
 
10
+ /**
11
+ * @typedef { import('./types.js').EnterFn } EnterFn
12
+ * @typedef { import('./types.js').ModdleElement } ModdleElement
13
+ * @typedef { import('./types.js').RuleDefinition } RuleDefinition
14
+ * @typedef { import('./types.js').Report } Report
15
+ */
16
+
17
+
10
18
  class Reporter {
19
+
20
+ /**
21
+ * @param { {
22
+ * moddleRoot: ModdleElement,
23
+ * rule: RuleDefinition
24
+ * } } options
25
+ */
11
26
  constructor({ moddleRoot, rule }) {
12
27
  this.rule = rule;
13
28
  this.moddleRoot = moddleRoot;
29
+
30
+ /**
31
+ * @type { Report[] }
32
+ */
14
33
  this.messages = [];
34
+
15
35
  this.report = this.report.bind(this);
16
36
  }
17
37
 
18
38
  /**
19
- * @param {string} id
20
- * @param {string} message
21
- * @param {string[]|Object} path
39
+ * @param { string } id
40
+ * @param { string } message
41
+ * @param { string[] | { path?: string[], [key: string]: any } } [path]
22
42
  *
23
43
  * @example
24
44
  *
25
- * Reporter.report('foo', 'Foo error', [ 'foo', 'bar', 'baz' ]);
26
- *
27
- * @example
45
+ * ```javascript
46
+ * reporter.report('foo', 'Foo error', [ 'foo', 'bar', 'baz' ]);
28
47
  *
29
- * Reporter.report('foo', 'Foo error', {
30
- * path: [ 'foo', 'bar', 'baz' ],
31
- * foo: 'foo'
48
+ * reporter.report('foo', 'Foo error', {
49
+ * path: [ 'foo', 'bar', 'baz' ],
50
+ * foo: 'foo'
32
51
  * });
52
+ * ```
33
53
  */
34
54
  report(id, message, path) {
35
55
  let report = {
@@ -55,6 +75,15 @@ class Reporter {
55
75
  }
56
76
  }
57
77
 
78
+ /**
79
+ * @param { {
80
+ * moddleRoot: ModdleElement,
81
+ * rule: RuleDefinition,
82
+ * config: any
83
+ * } } options
84
+ *
85
+ * @return { Report[] } lint reports
86
+ */
58
87
  module.exports = function testRule({ moddleRoot, rule }) {
59
88
  const reporter = new Reporter({ rule, moddleRoot });
60
89
 
@@ -70,8 +99,8 @@ module.exports = function testRule({ moddleRoot, rule }) {
70
99
  }
71
100
 
72
101
  traverse(moddleRoot, {
73
- enter: enter ? (node) => enter(node, reporter) : null,
74
- leave: leave ? (node) => leave(node, reporter) : null
102
+ enter: enter ? (node) => enter(node, reporter) : undefined,
103
+ leave: leave ? (node) => leave(node, reporter) : undefined
75
104
  });
76
105
 
77
106
  return reporter.messages;
@@ -1,6 +1,7 @@
1
+ // @ts-expect-error 'missing <bpmn-moddle> types'
1
2
  const BpmnModdle = require('bpmn-moddle');
2
3
 
3
- const readFileSync = require('fs').readFileSync;
4
+ const { readFileSync } = require('node:fs');
4
5
 
5
6
 
6
7
  /**
@@ -5,8 +5,13 @@ const assert = require('assert');
5
5
  const Linter = require('../linter');
6
6
 
7
7
  function createResolver(rule) {
8
+
8
9
  return {
9
- resolveRule: () => Promise.resolve(rule)
10
+ resolveRule: () => Promise.resolve(rule),
11
+
12
+ resolveConfig: () => {
13
+ throw new Error('not implemented');
14
+ }
10
15
  };
11
16
  }
12
17
 
@@ -57,7 +62,7 @@ function verify(ruleName, rule, testCases) {
57
62
 
58
63
  testCases.invalid.forEach(({ config = {}, it: _it, moddleElement, name, report }, idx) => (
59
64
 
60
- (_it || it)(getTitle(idx, name), function() {
65
+ (_it || it)(getTitle(idx, name), async function() {
61
66
 
62
67
  if (!Array.isArray(report)) {
63
68
  report = [
@@ -65,9 +70,14 @@ function verify(ruleName, rule, testCases) {
65
70
  ];
66
71
  }
67
72
 
73
+ const {
74
+ meta
75
+ } = await linter.resolveRule(name, config);
76
+
68
77
  const expectedResults = report.map(report => {
69
78
  return {
70
79
  ...report,
80
+ meta,
71
81
  category: 'error'
72
82
  };
73
83
  });
package/lib/traverse.js CHANGED
@@ -1,19 +1,22 @@
1
1
  /**
2
- * @typedef { import('./types.js').EnterFn } EnterFn
3
- * @typedef { import('./types.js').LeaveFn } LeaveFn
2
+ * @typedef { import('./types.js').ModdleElement } ModdleElement
3
+ *
4
+ * @typedef { (element: ModdleElement) => boolean | void } EnterFn
5
+ * @typedef { (element: ModdleElement) => void } LeaveFn
4
6
  */
5
7
 
8
+
6
9
  /**
7
10
  * Traverse a moddle tree, depth first from top to bottom
8
11
  * and call the passed visitor fn.
9
12
  *
10
- * @param {ModdleElement} element
11
- * @param {{ enter?: EnterFn; leave?: LeaveFn }} options
13
+ * @param { ModdleElement } element
14
+ * @param { { enter?: EnterFn; leave?: LeaveFn } } options
12
15
  */
13
16
  module.exports = function traverse(element, options) {
14
17
 
15
- const enter = options.enter || null;
16
- const leave = options.leave || null;
18
+ const enter = options.enter;
19
+ const leave = options.leave;
17
20
 
18
21
  const enterSubTree = enter && enter(element);
19
22