eslint-plugin-crisp 1.7.8 → 1.7.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-crisp",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "Custom ESLint Rules for Crisp",
5
5
  "author": "Crisp IM SAS",
6
6
  "main": "index.js",
@@ -94,6 +94,47 @@ function isReference(node) {
94
94
  );
95
95
  }
96
96
 
97
+ // Checks an empty object or array literal (eg. `{}` or `[]`)
98
+ function isEmptyLiteral(node) {
99
+ return (
100
+ (node.type === "ObjectExpression" && node.properties.length === 0) ||
101
+ (node.type === "ArrayExpression" && node.elements.length === 0)
102
+ );
103
+ }
104
+
105
+ // Checks a fallback expression like `value || "none"` or `value ?? other`
106
+ function isFallback(node) {
107
+ node = unwrapTransparentExpression(node);
108
+
109
+ if (
110
+ !node ||
111
+ node.type !== "LogicalExpression" ||
112
+ (node.operator !== "||" && node.operator !== "??")
113
+ ) {
114
+ return false;
115
+ }
116
+
117
+ const isOperandValid = (operand) => {
118
+ operand = unwrapTransparentExpression(operand);
119
+
120
+ return (
121
+ operand.type === "Literal" ||
122
+ isEmptyLiteral(operand) ||
123
+ isReference(operand) ||
124
+ isFallback(operand)
125
+ );
126
+ };
127
+
128
+ return isReference(node.left) || isFallback(node.left)
129
+ ? isOperandValid(node.right)
130
+ : false;
131
+ }
132
+
133
+ // Checks a logger argument value is a reference, a fallback or an object of those
134
+ function isAllowedValue(node) {
135
+ return isReference(node) || isFallback(node) || isReferenceObject(node);
136
+ }
137
+
97
138
  // Checks an inline object only contains references (no computed values)
98
139
  function isReferenceObject(node) {
99
140
  node = unwrapTransparentExpression(node);
@@ -115,10 +156,65 @@ function isReferenceObject(node) {
115
156
  return false;
116
157
  }
117
158
 
118
- return isReference(property.value) || isReferenceObject(property.value);
159
+ return isAllowedValue(property.value);
119
160
  });
120
161
  }
121
162
 
163
+ // Checks a node is a `logger.levels.<level>` member expression
164
+ function isLevelsReference(node) {
165
+ node = unwrapTransparentExpression(node);
166
+
167
+ return (
168
+ Boolean(node) &&
169
+ node.type === "MemberExpression" &&
170
+ node.object.type === "MemberExpression" &&
171
+ getPropertyName(node.object) === "levels" &&
172
+ node.object.object.type === "MemberExpression" &&
173
+ getPropertyName(node.object.object) === "logger"
174
+ );
175
+ }
176
+
177
+ // Checks a condition tests a logger level (eg. `logger.levels.error === true && x`)
178
+ function isLevelCheck(node) {
179
+ node = unwrapTransparentExpression(node);
180
+
181
+ if (!node) {
182
+ return false;
183
+ }
184
+
185
+ if (node.type === "LogicalExpression" && node.operator === "&&") {
186
+ return isLevelCheck(node.left) || isLevelCheck(node.right);
187
+ }
188
+
189
+ if (
190
+ node.type === "BinaryExpression" &&
191
+ (node.operator === "===" || node.operator === "==")
192
+ ) {
193
+ return isLevelsReference(node.left) || isLevelsReference(node.right);
194
+ }
195
+
196
+ return isLevelsReference(node);
197
+ }
198
+
199
+ // Checks a node is inside the consequent of a logger level guard
200
+ function isGuarded(ancestors, node) {
201
+ const chain = [...ancestors, node];
202
+
203
+ for (let index = chain.length - 2; index >= 0; index--) {
204
+ const ancestor = chain[index];
205
+
206
+ if (
207
+ ancestor.type === "IfStatement" &&
208
+ chain[index + 1] === ancestor.consequent &&
209
+ isLevelCheck(ancestor.test)
210
+ ) {
211
+ return true;
212
+ }
213
+ }
214
+
215
+ return false;
216
+ }
217
+
122
218
  export default {
123
219
  meta: {
124
220
  type: "suggestion",
@@ -132,7 +228,7 @@ export default {
132
228
  message:
133
229
  "Logger messages must be single-line, double-quoted string literals.",
134
230
  reference:
135
- "Logger interpolation arguments must be references or inline objects of references, not computed expressions."
231
+ "Logger interpolation arguments must be references, fallbacks (`||`, `??`) or inline objects of those, not computed expressions."
136
232
  }
137
233
  },
138
234
 
@@ -145,6 +241,11 @@ export default {
145
241
  return;
146
242
  }
147
243
 
244
+ // Guarded by a log level check: computed values are allowed
245
+ if (isGuarded(sourceCode.getAncestors(node), node)) {
246
+ return;
247
+ }
248
+
148
249
  const [message, ...values] = node.arguments;
149
250
 
150
251
  if (
@@ -161,7 +262,7 @@ export default {
161
262
  }
162
263
 
163
264
  for (const value of values) {
164
- if (!isReference(value) && !isReferenceObject(value)) {
265
+ if (!isAllowedValue(value)) {
165
266
  context.report({
166
267
  node: value,
167
268
  messageId: "reference"
@@ -15,6 +15,16 @@ ruleTester.run("logger-arguments", rule, {
15
15
  value
16
16
  );`,
17
17
  "App.logger.info(\"This intentionally long logger message may exceed the usual source line length when necessary.\");",
18
+ "App.logger.debug(\"Loaded value: %s\", value || \"default\");",
19
+ "App.logger.debug(\"Loaded value: %s\", this.value ?? \"none\");",
20
+ "App.logger.debug(\"Loaded value: %s\", first || second || \"none\");",
21
+ "App.logger.debug(\"Loaded value: %s\", { user: userId || \"none\" });",
22
+ "if (App.logger.levels.error === true) { App.logger.error(\"Purge failed: %s\", buckets.join(\", \"), error); }",
23
+ "if (App.logger.levels.warn === true) { App.logger.warn(fn_log_format()); }",
24
+ "if (App.logger.levels.error === true && other) { if (x) { App.logger.error(\"Slow: %s\", a + b); } }",
25
+ "if (this._app.logger.levels.debug) { this._app.logger.debug(\"Count: %s\", list.length + 1); }",
26
+ "App.logger.error(\"Failed\", { params: (request.params || {}) });",
27
+ "App.logger.error(\"Failed: %s\", list || []);",
18
28
  "App.logger.warn(\"Shutting down\", { bufferedCount });",
19
29
  "App.logger.warn(\"Shutting down\", { count: this.count, ...meta });",
20
30
  "App.logger.warn(\"Shutting down\", { nested: { bufferedCount } }, logMeta);",
@@ -80,7 +90,15 @@ ruleTester.run("logger-arguments", rule, {
80
90
  ]
81
91
  },
82
92
  {
83
- code: "App.logger.debug(\"Loaded value: %s\", value || \"default\");",
93
+ code: "App.logger.debug(\"Loaded value: %s\", value || compute());",
94
+ errors: [
95
+ {
96
+ messageId: "reference"
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ code: "App.logger.debug(\"Loaded value: %s\", value && \"default\");",
84
102
  errors: [
85
103
  {
86
104
  messageId: "reference"
@@ -111,6 +129,22 @@ ruleTester.run("logger-arguments", rule, {
111
129
  }
112
130
  ]
113
131
  },
132
+ {
133
+ code: "if (App.logger.levels.error === true) {} else { App.logger.error(\"Purge: %s\", buckets.join(\", \")); }",
134
+ errors: [
135
+ {
136
+ messageId: "reference"
137
+ }
138
+ ]
139
+ },
140
+ {
141
+ code: "if (enabled) { App.logger.error(\"Purge: %s\", buckets.join(\", \")); }",
142
+ errors: [
143
+ {
144
+ messageId: "reference"
145
+ }
146
+ ]
147
+ },
114
148
  {
115
149
  code: "App.logger.debug(\"Retry count: %s\", 3);",
116
150
  errors: [
@@ -120,7 +154,7 @@ ruleTester.run("logger-arguments", rule, {
120
154
  ]
121
155
  },
122
156
  {
123
- code: "App.logger.debug(\"Values: %s / %s\", values.join(\", \"), fallback || \"none\");",
157
+ code: "App.logger.debug(\"Values: %s / %s\", values.join(\", \"), fallback || compute());",
124
158
  errors: [
125
159
  {
126
160
  messageId: "reference"