@ethlete/eslint-plugin 1.0.0-next.30 → 1.0.0-next.31

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-next.31
4
+
5
+ ### Patch Changes
6
+
7
+ - `no-silent-catch-error` also flags a `try/catch` whose catch ignores the error and returns a fallback value, such as `catch { return [] }`.
8
+
3
9
  ## 1.0.0-next.30
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethlete/eslint-plugin",
3
- "version": "1.0.0-next.30",
3
+ "version": "1.0.0-next.31",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/ethlete-io/ethdk",
@@ -2,13 +2,16 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * Disallow an RxJS catchError whose handler ignores the error and returns of(...), EMPTY or NEVER.
5
+ * Disallow an RxJS catchError whose handler ignores the error and returns of(...), EMPTY or NEVER,
6
+ * and, with the `tryCatch` option, a try/catch whose catch ignores the error and returns a fallback value.
6
7
  *
7
8
  * BAD:
8
9
  * source$.pipe(catchError(() => of(null)));
10
+ * try { return read(); } catch { return []; }
9
11
  *
10
12
  * GOOD:
11
13
  * source$.pipe(catchError((error) => { this.error.set(error); return of(null); }));
14
+ * try { return read(); } catch (error) { report(error); return []; }
12
15
  */
13
16
 
14
17
  const SWALLOWING_CONSTANTS = new Set(['EMPTY', 'NEVER']);
@@ -24,6 +27,10 @@ const isSwallowingValue = (node) => {
24
27
  const collectReturnedValues = (fn) => {
25
28
  if (fn.body.type !== 'BlockStatement') return [fn.body];
26
29
 
30
+ return collectReturnsIn(fn.body);
31
+ };
32
+
33
+ const collectReturnsIn = (block) => {
27
34
  const values = [];
28
35
 
29
36
  const visit = (node) => {
@@ -52,7 +59,7 @@ const collectReturnedValues = (fn) => {
52
59
  }
53
60
  };
54
61
 
55
- fn.body.body.forEach(visit);
62
+ block.body.forEach(visit);
56
63
 
57
64
  return values;
58
65
  };
@@ -62,37 +69,50 @@ const noSilentCatchError = {
62
69
  meta: {
63
70
  type: 'suggestion',
64
71
  docs: {
65
- description: 'Disallow a catchError handler that ignores the error and swallows it with of(), EMPTY or NEVER.',
72
+ description:
73
+ 'Disallow a catchError handler that ignores the error and swallows it with of(), EMPTY or NEVER, and with the `tryCatch` option a catch clause that ignores the error and returns a fallback.',
66
74
  recommended: false,
67
75
  },
68
76
  messages: {
69
77
  silentCatchError:
70
78
  'The error disappears silently. Use it (log it, store it in a signal, rethrow it) or disable this rule with a reason.',
71
79
  },
72
- schema: [],
80
+ schema: [
81
+ {
82
+ type: 'object',
83
+ properties: { tryCatch: { type: 'boolean' } },
84
+ additionalProperties: false,
85
+ },
86
+ ],
73
87
  },
74
88
  create(context) {
75
89
  const sourceCode = context.sourceCode;
90
+ const checkTryCatch = context.options[0]?.tryCatch === true;
76
91
 
77
- const ignoresError = (fn) => {
78
- const [param] = fn.params;
79
-
92
+ const ignoresError = (scopeNode, param) => {
80
93
  if (!param) return true;
81
94
  if (param.type !== 'Identifier') return false;
82
95
 
83
- const variable = sourceCode.getDeclaredVariables(fn).find((v) => v.name === param.name);
96
+ const variable = sourceCode.getDeclaredVariables(scopeNode).find((v) => v.name === param.name);
84
97
 
85
98
  return !variable || variable.references.length === 0;
86
99
  };
87
100
 
88
101
  return {
102
+ CatchClause(node) {
103
+ if (!checkTryCatch) return;
104
+ if (!ignoresError(node, node.param)) return;
105
+ if (collectReturnsIn(node.body).length === 0) return;
106
+
107
+ context.report({ node, messageId: 'silentCatchError' });
108
+ },
89
109
  CallExpression(node) {
90
110
  if (node.callee.type !== 'Identifier' || node.callee.name !== 'catchError') return;
91
111
 
92
112
  const [handler] = node.arguments;
93
113
 
94
114
  if (!handler || (handler.type !== 'ArrowFunctionExpression' && handler.type !== 'FunctionExpression')) return;
95
- if (!ignoresError(handler)) return;
115
+ if (!ignoresError(handler, handler.params[0])) return;
96
116
 
97
117
  if (collectReturnedValues(handler).some(isSwallowingValue)) {
98
118
  context.report({ node: handler, messageId: 'silentCatchError' });