@logtape/lint 2.2.0-dev.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 (54) hide show
  1. package/LICENSE +20 -0
  2. package/dist/core/ast.cjs +517 -0
  3. package/dist/core/ast.js +506 -0
  4. package/dist/core/ast.js.map +1 -0
  5. package/dist/deno/plugin.cjs +332 -0
  6. package/dist/deno/plugin.d.cts +46 -0
  7. package/dist/deno/plugin.d.cts.map +1 -0
  8. package/dist/deno/plugin.d.ts +46 -0
  9. package/dist/deno/plugin.d.ts.map +1 -0
  10. package/dist/deno/plugin.js +333 -0
  11. package/dist/deno/plugin.js.map +1 -0
  12. package/dist/eslint/plugin.cjs +51 -0
  13. package/dist/eslint/plugin.d.cts +26 -0
  14. package/dist/eslint/plugin.d.cts.map +1 -0
  15. package/dist/eslint/plugin.d.ts +26 -0
  16. package/dist/eslint/plugin.d.ts.map +1 -0
  17. package/dist/eslint/plugin.js +44 -0
  18. package/dist/eslint/plugin.js.map +1 -0
  19. package/dist/mod.cjs +15 -0
  20. package/dist/mod.d.cts +6 -0
  21. package/dist/mod.d.ts +6 -0
  22. package/dist/mod.js +7 -0
  23. package/dist/rules/no-message-interpolation.cjs +52 -0
  24. package/dist/rules/no-message-interpolation.d.cts +23 -0
  25. package/dist/rules/no-message-interpolation.d.cts.map +1 -0
  26. package/dist/rules/no-message-interpolation.d.ts +23 -0
  27. package/dist/rules/no-message-interpolation.d.ts.map +1 -0
  28. package/dist/rules/no-message-interpolation.js +53 -0
  29. package/dist/rules/no-message-interpolation.js.map +1 -0
  30. package/dist/rules/no-unawaited-log.cjs +67 -0
  31. package/dist/rules/no-unawaited-log.d.cts +21 -0
  32. package/dist/rules/no-unawaited-log.d.cts.map +1 -0
  33. package/dist/rules/no-unawaited-log.d.ts +21 -0
  34. package/dist/rules/no-unawaited-log.d.ts.map +1 -0
  35. package/dist/rules/no-unawaited-log.js +68 -0
  36. package/dist/rules/no-unawaited-log.js.map +1 -0
  37. package/dist/rules/prefer-lazy-evaluation.cjs +59 -0
  38. package/dist/rules/prefer-lazy-evaluation.d.cts +22 -0
  39. package/dist/rules/prefer-lazy-evaluation.d.cts.map +1 -0
  40. package/dist/rules/prefer-lazy-evaluation.d.ts +22 -0
  41. package/dist/rules/prefer-lazy-evaluation.d.ts.map +1 -0
  42. package/dist/rules/prefer-lazy-evaluation.js +60 -0
  43. package/dist/rules/prefer-lazy-evaluation.js.map +1 -0
  44. package/dist/rules/require-meta-sink.cjs +75 -0
  45. package/dist/rules/require-meta-sink.d.cts +27 -0
  46. package/dist/rules/require-meta-sink.d.cts.map +1 -0
  47. package/dist/rules/require-meta-sink.d.ts +27 -0
  48. package/dist/rules/require-meta-sink.d.ts.map +1 -0
  49. package/dist/rules/require-meta-sink.js +76 -0
  50. package/dist/rules/require-meta-sink.js.map +1 -0
  51. package/dist/utils.cjs +82 -0
  52. package/dist/utils.js +82 -0
  53. package/dist/utils.js.map +1 -0
  54. package/package.json +90 -0
@@ -0,0 +1,506 @@
1
+ //#region src/core/ast.ts
2
+ /**
3
+ * Shared, host-agnostic AST analysis used by both the ESLint rules
4
+ * (`../rules/`) and the Deno Lint plugin (`../deno/plugin.ts`).
5
+ *
6
+ * Every function here works on plain ESTree-shaped AST nodes and depends only
7
+ * on `node.type` / `node.parent` style traversal, so the same logic runs under
8
+ * ESLint, Oxlint, and Deno Lint. Divergences between the host ASTs (e.g. Deno
9
+ * exposing `TemplateElement.cooked` directly where ESTree nests it under
10
+ * `value.cooked`) are absorbed here with `??` fallbacks, so an edge case is
11
+ * fixed once rather than in two drifting copies.
12
+ *
13
+ * This module must not import from `eslint`, not even types: the Deno plugin
14
+ * imports it and must stay loadable without the ESLint package present.
15
+ *
16
+ * @module
17
+ */
18
+ /**
19
+ * Log method names that the LogTape lint rules check.
20
+ */
21
+ const LOG_METHODS = new Set([
22
+ "trace",
23
+ "debug",
24
+ "info",
25
+ "warn",
26
+ "warning",
27
+ "error",
28
+ "fatal"
29
+ ]);
30
+ /**
31
+ * AST node types that introduce their own function scope.
32
+ */
33
+ const ASYNC_FUNCTION_TYPES = new Set([
34
+ "ArrowFunctionExpression",
35
+ "FunctionExpression",
36
+ "FunctionDeclaration"
37
+ ]);
38
+ /**
39
+ * Maximum depth for the recursive AST scans. The AST is finite, so this is a
40
+ * safety net against pathological nesting rather than an expected limit; it is
41
+ * generous enough to cover deeply nested log property objects (complex API
42
+ * payloads, ORM entities) without false negatives.
43
+ */
44
+ const MAX_RECURSION_DEPTH = 100;
45
+ /**
46
+ * Whether an import source refers to the LogTape core package. Accepts the
47
+ * bare specifier (`@logtape/logtape`) as well as direct Deno-style `jsr:` and
48
+ * `npm:` specifiers with an optional version suffix (e.g.
49
+ * `jsr:@logtape/logtape` or `npm:@logtape/logtape@^1.0.0`).
50
+ */
51
+ function isLogtapeImportSource(source) {
52
+ return typeof source === "string" && /^(?:(?:jsr|npm):)?@logtape\/logtape(?:@[^/]+)?$/.test(source);
53
+ }
54
+ /**
55
+ * Unwrap TypeScript type-assertion wrappers around an expression (`x as T`,
56
+ * `<T>x`, `x satisfies T`, `x!`) so the rules analyze the underlying node.
57
+ * Each wrapper exposes the inner node as `.expression`. Returns the node
58
+ * unchanged when it is not such a wrapper.
59
+ */
60
+ function unwrapTypeAssertion(node) {
61
+ while (node && (node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression")) node = node.expression;
62
+ return node;
63
+ }
64
+ /**
65
+ * Resolve the log method name from a member-expression callee
66
+ * (`logger.debug` -> `"debug"`), supporting computed string-literal access
67
+ * (`logger["debug"]`). Returns `null` for a computed non-literal property or a
68
+ * non-member callee.
69
+ */
70
+ function logMethodName(callee) {
71
+ if (!callee || callee.type !== "MemberExpression") return null;
72
+ if (!callee.computed) return callee.property?.name ?? null;
73
+ return callee.property?.type === "Literal" && typeof callee.property.value === "string" ? callee.property.value : null;
74
+ }
75
+ /**
76
+ * Recursively check whether an AST node contains an eagerly evaluated call
77
+ * anywhere in its subtree. Only inspects own-enumerable child nodes, skipping
78
+ * metadata fields. A LogTape `lazy(...)` call (whose local names are in
79
+ * `lazyNames`) is already deferred, so it is not counted as eager; its
80
+ * arguments are still inspected, so `lazy(expensive())` is caught while
81
+ * `lazy(() => expensive())` is not.
82
+ */
83
+ function containsCallExpression(node, lazyNames, depth = 0) {
84
+ if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== "object") return false;
85
+ const isLazyCall = node.type === "CallExpression" && node.callee?.type === "Identifier" && lazyNames.has(node.callee.name);
86
+ if (!isLazyCall && (node.type === "CallExpression" || node.type === "OptionalCallExpression" || node.type === "NewExpression" || node.type === "TaggedTemplateExpression" || node.type === "ImportExpression")) return true;
87
+ if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") return false;
88
+ for (const key in node) {
89
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "parent" || key === "range") continue;
90
+ const child = node[key];
91
+ if (Array.isArray(child)) {
92
+ for (const item of child) if (typeof item === "object" && item !== null && containsCallExpression(item, lazyNames, depth + 1)) return true;
93
+ } else if (typeof child === "object" && child !== null) {
94
+ if (containsCallExpression(child, lazyNames, depth + 1)) return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ /**
100
+ * Recursively check whether a node contains an `AwaitExpression` or
101
+ * `YieldExpression` in the same function scope. Does not descend into nested
102
+ * function bodies.
103
+ */
104
+ function containsAwaitOrYield(node, depth = 0) {
105
+ if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== "object") return false;
106
+ if (node.type === "AwaitExpression" || node.type === "YieldExpression") return true;
107
+ if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") return false;
108
+ for (const key in node) {
109
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "parent" || key === "range") continue;
110
+ const child = node[key];
111
+ if (Array.isArray(child)) {
112
+ for (const item of child) if (typeof item === "object" && item !== null && containsAwaitOrYield(item, depth + 1)) return true;
113
+ } else if (typeof child === "object" && child !== null) {
114
+ if (containsAwaitOrYield(child, depth + 1)) return true;
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ /**
120
+ * From a log call's argument list, select the eager properties object and note
121
+ * whether it came from the properties-only overload. The properties object is
122
+ * the second argument in the message+properties form
123
+ * (`logger.debug("msg", { ... })`) or the first argument in the
124
+ * properties-only form (`logger.debug({ ... })`). TypeScript type assertions
125
+ * (e.g. `{ ... } as const`) are unwrapped first. Returns `null` when neither
126
+ * argument is an object literal.
127
+ *
128
+ * `propsObject` is the unwrapped object (for detection and the report
129
+ * location); `fixTarget` is the original, still-wrapped argument node, so the
130
+ * autofix replaces the whole `{ ... } as const` rather than leaving the
131
+ * assertion dangling on the new callback. When the argument is not wrapped the
132
+ * two are the same node.
133
+ */
134
+ function selectLazyPropsObject(args) {
135
+ const firstRaw = args?.[0];
136
+ const secondRaw = args?.[1];
137
+ const firstArg = unwrapTypeAssertion(firstRaw);
138
+ const secondArg = unwrapTypeAssertion(secondRaw);
139
+ if (firstArg && firstArg.type === "ObjectExpression") return {
140
+ propsObject: firstArg,
141
+ fixTarget: firstRaw,
142
+ propertiesOnly: true
143
+ };
144
+ if (secondArg && secondArg.type === "ObjectExpression") return {
145
+ propsObject: secondArg,
146
+ fixTarget: secondRaw,
147
+ propertiesOnly: false
148
+ };
149
+ return null;
150
+ }
151
+ /**
152
+ * Whether any property (or spread) of a properties object contains an eager
153
+ * call that would benefit from lazy evaluation.
154
+ */
155
+ function propsHaveEagerCall(propsObject, lazyNames) {
156
+ return propsObject.properties?.some((prop) => prop.type === "Property" && (containsCallExpression(prop.value, lazyNames) || prop.computed && containsCallExpression(prop.key, lazyNames)) || prop.type === "SpreadElement" && containsCallExpression(prop.argument, lazyNames)) ?? false;
157
+ }
158
+ /**
159
+ * Whether `node` is an async function literal (arrow or function expression).
160
+ */
161
+ function isAsyncFunctionExpr(node) {
162
+ return (node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression") && node.async === true;
163
+ }
164
+ /**
165
+ * Walk up the parent chain to find the nearest enclosing function. Returns
166
+ * `null` if the top of the tree is reached without finding one.
167
+ */
168
+ function findEnclosingFunction(node) {
169
+ let current = node.parent;
170
+ while (current) {
171
+ if (ASYNC_FUNCTION_TYPES.has(current.type)) return current;
172
+ current = current.parent;
173
+ }
174
+ return null;
175
+ }
176
+ /**
177
+ * Whether `fn` is a function passed directly as a call argument (e.g. an array
178
+ * `.map()`/`.forEach()` callback). Such a function's return value is decided
179
+ * by the receiving call, so a promise it returns may be awaited or discarded.
180
+ */
181
+ function isCallArgumentFunction(fn) {
182
+ const parent = fn?.parent;
183
+ return parent?.type === "CallExpression" && parent.callee !== fn && (parent.arguments?.includes(fn) ?? false);
184
+ }
185
+ /**
186
+ * Array iteration methods that ignore, coerce, or merely thread the callback's
187
+ * return value, so a promise returned from the callback is not awaited per
188
+ * call. `reduce`/`reduceRight` pass it on as the next accumulator (only the
189
+ * final accumulator is the result), so earlier per-item promises are dropped.
190
+ */
191
+ const DISCARDING_ARRAY_METHODS = new Set([
192
+ "forEach",
193
+ "filter",
194
+ "find",
195
+ "findLast",
196
+ "findIndex",
197
+ "findLastIndex",
198
+ "some",
199
+ "every",
200
+ "reduce",
201
+ "reduceRight"
202
+ ]);
203
+ /**
204
+ * Global "fire and forget" functions that ignore their callback's return value
205
+ * entirely, so a promise returned from the callback is never awaited.
206
+ */
207
+ const FIRE_AND_FORGET_GLOBALS = new Set([
208
+ "setTimeout",
209
+ "setInterval",
210
+ "setImmediate",
211
+ "queueMicrotask",
212
+ "requestAnimationFrame",
213
+ "requestIdleCallback"
214
+ ]);
215
+ /**
216
+ * Whether `fn` is a callback argument to a call that ignores or coerces its
217
+ * callback's return value: an array method like `forEach`/`filter`/`some`, or a
218
+ * fire-and-forget global like `setTimeout`/`queueMicrotask`. Such a call does
219
+ * not propagate the callback's promise, so an async log returned from it is
220
+ * dropped and the walk must stop there rather than continue to an outer
221
+ * await/return.
222
+ */
223
+ function isDiscardedCallbackArgument(fn) {
224
+ const parent = fn?.parent;
225
+ if (!parent || parent.type !== "CallExpression") return false;
226
+ if (parent.callee === fn) return false;
227
+ if (!(parent.arguments?.includes(fn) ?? false)) return false;
228
+ const callee = parent.callee;
229
+ if (callee?.type === "Identifier" && FIRE_AND_FORGET_GLOBALS.has(callee.name)) return true;
230
+ const methodName = logMethodName(callee);
231
+ return methodName !== null && DISCARDING_ARRAY_METHODS.has(methodName);
232
+ }
233
+ /**
234
+ * Whether `node` is the result of a `map()`/`flatMap()` call. Such a call
235
+ * returns an array of the callback's return values, so awaiting or returning it
236
+ * does not await the element promises; only a Promise combinator
237
+ * (`Promise.all`/`allSettled`) awaits those.
238
+ */
239
+ function isMapResult(node) {
240
+ return node?.type === "CallExpression" && node.callee?.type === "MemberExpression" && !node.callee.computed && node.callee.property?.type === "Identifier" && (node.callee.property.name === "map" || node.callee.property.name === "flatMap");
241
+ }
242
+ /**
243
+ * Array methods that return a new array holding the same elements, so a
244
+ * `map()`/`flatMap()` result chained through them is still an array of the
245
+ * original promises (e.g. `arr.map(cb).filter(Boolean)`). Methods that unwrap
246
+ * to a single element (`find`, `at`) or transform the elements (`map`,
247
+ * `reduce`) are deliberately excluded.
248
+ */
249
+ const ELEMENT_PRESERVING_ARRAY_METHODS = new Set([
250
+ "filter",
251
+ "slice",
252
+ "concat",
253
+ "flat",
254
+ "reverse",
255
+ "sort",
256
+ "toSorted",
257
+ "toReversed",
258
+ "with"
259
+ ]);
260
+ /**
261
+ * Whether `node` evaluates to an array of the promises produced by a
262
+ * `map()`/`flatMap()`, either directly or chained through element-preserving
263
+ * array methods (`arr.map(cb).filter(...).slice(...)`). Awaiting or returning
264
+ * such an array awaits the array, not the element promises.
265
+ */
266
+ function isMapResultChain(node, depth = 0) {
267
+ if (depth > MAX_RECURSION_DEPTH || !node) return false;
268
+ if (isMapResult(node)) return true;
269
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && !node.callee.computed && node.callee.property?.type === "Identifier" && ELEMENT_PRESERVING_ARRAY_METHODS.has(node.callee.property.name)) return isMapResultChain(node.callee.object, depth + 1);
270
+ return false;
271
+ }
272
+ /**
273
+ * Whether `node` is an expression that is syntactically a promise. Recognizes
274
+ * `x.then(...)` / `.catch(...)` / `.finally(...)`, `new Promise(...)`, and
275
+ * `Promise.resolve/reject/all/allSettled/race/any(...)`. This cannot see a
276
+ * promise returned by an opaque call (e.g. `fetchData()` whose return type is a
277
+ * promise), which a syntactic lint rule has no type information for.
278
+ */
279
+ function isPromiseReturningExpr(node) {
280
+ if (!node) return false;
281
+ if (node.type === "CallExpression" && node.callee?.type === "MemberExpression" && !node.callee.computed && node.callee.property?.type === "Identifier") {
282
+ const name = node.callee.property.name;
283
+ if (name === "then" || name === "catch" || name === "finally") return true;
284
+ if (node.callee.object?.type === "Identifier" && node.callee.object.name === "Promise") return true;
285
+ }
286
+ if (node.type === "NewExpression" && node.callee?.type === "Identifier" && node.callee.name === "Promise") return true;
287
+ return false;
288
+ }
289
+ /**
290
+ * Whether a block contains a `return <promise>` statement, without descending
291
+ * into nested functions (whose returns belong to them, not to the block's
292
+ * function).
293
+ */
294
+ function blockReturnsPromise(node, depth = 0) {
295
+ if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== "object") return false;
296
+ if (node.type === "ReturnStatement") return isPromiseReturningExpr(node.argument);
297
+ if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") return false;
298
+ for (const key in node) {
299
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "parent" || key === "range") continue;
300
+ const child = node[key];
301
+ if (Array.isArray(child)) {
302
+ for (const item of child) if (typeof item === "object" && item !== null && blockReturnsPromise(item, depth + 1)) return true;
303
+ } else if (typeof child === "object" && child !== null) {
304
+ if (blockReturnsPromise(child, depth + 1)) return true;
305
+ }
306
+ }
307
+ return false;
308
+ }
309
+ /**
310
+ * Whether `fn` is a function (arrow, function expression, or function
311
+ * declaration) whose body returns a syntactically promise-typed value, even
312
+ * though it is not declared `async`. LogTape awaits any promise the lazy
313
+ * callback returns, e.g. `() => fetchData().then((data) => ({ data }))`, so a
314
+ * non-async helper resolved by reference needs the same handling as an inline
315
+ * one.
316
+ */
317
+ function isPromiseReturningCallback(fn) {
318
+ if (fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression" && fn?.type !== "FunctionDeclaration") return false;
319
+ const body = fn.body;
320
+ if (body && body.type !== "BlockStatement") return isPromiseReturningExpr(body);
321
+ return blockReturnsPromise(body);
322
+ }
323
+ /**
324
+ * Whether `arrayNode` is the argument array of a Promise combinator that awaits
325
+ * every element, i.e. `Promise.all([...])` or `Promise.allSettled([...])`.
326
+ * Those consume all element promises, so a log promise inside the array is
327
+ * still awaited when the combinator is awaited. `Promise.race`/`Promise.any`
328
+ * are excluded: they can settle on another promise before the log promise
329
+ * resolves, so the log write is not guaranteed to flush. A bare array literal,
330
+ * by contrast, does not preserve promise semantics at all.
331
+ */
332
+ function isPromiseCombinatorArrayArg(arrayNode) {
333
+ const parent = arrayNode.parent;
334
+ if (!parent || parent.type !== "CallExpression") return false;
335
+ if (!(parent.arguments?.includes(arrayNode) ?? false)) return false;
336
+ const callee = parent.callee;
337
+ return callee?.type === "MemberExpression" && !callee.computed && callee.object?.type === "Identifier" && callee.object.name === "Promise" && callee.property?.type === "Identifier" && ["all", "allSettled"].includes(callee.property.name);
338
+ }
339
+ /**
340
+ * Walk the ancestor chain of a log call to decide whether the promise it
341
+ * returns is awaited, returned, or otherwise propagated to a caller that can
342
+ * await it. Returns `true` when the promise is handled and the call therefore
343
+ * needs no `await`.
344
+ *
345
+ * Handled: the call is awaited, returned from a non-discarding function, the
346
+ * concise body of a non-discarding arrow, or chained with
347
+ * `.then`/`.catch`/`.finally`, or it sits inside `Promise.all`/`allSettled`.
348
+ * Not handled (the walk stops and returns `false`): the promise is wrapped in
349
+ * an object/array literal, awaiting a `map()`/`flatMap()` array (which awaits
350
+ * the array, not its element promises), dropped by a discarding callback
351
+ * (`forEach` etc.), or it reaches a statement boundary unconsumed.
352
+ */
353
+ function isLogPromiseHandled(node) {
354
+ let current = node;
355
+ while (current) {
356
+ const ancestor = current.parent;
357
+ if (!ancestor) break;
358
+ if (ancestor.type === "AwaitExpression") {
359
+ if (isMapResultChain(current)) break;
360
+ return true;
361
+ }
362
+ if (ancestor.type === "ReturnStatement") {
363
+ if (isMapResultChain(current)) break;
364
+ const fn = findEnclosingFunction(ancestor);
365
+ if (fn && isCallArgumentFunction(fn)) {
366
+ if (isDiscardedCallbackArgument(fn)) break;
367
+ current = fn;
368
+ continue;
369
+ }
370
+ return true;
371
+ }
372
+ if (ancestor.type === "ArrowFunctionExpression" && ancestor.body === current) {
373
+ if (!isCallArgumentFunction(ancestor)) return true;
374
+ if (isDiscardedCallbackArgument(ancestor)) break;
375
+ }
376
+ if (ancestor.type === "MemberExpression") {
377
+ const prop = ancestor.property;
378
+ const isPromiseMethod = !ancestor.computed ? prop?.type === "Identifier" && [
379
+ "then",
380
+ "catch",
381
+ "finally"
382
+ ].includes(prop.name) : prop?.type === "Literal" && [
383
+ "then",
384
+ "catch",
385
+ "finally"
386
+ ].includes(prop.value);
387
+ if (isPromiseMethod) {
388
+ const grandAncestor = ancestor.parent;
389
+ if (grandAncestor?.type === "CallExpression" && grandAncestor.callee === ancestor) return true;
390
+ }
391
+ }
392
+ if (ancestor.type === "ObjectExpression") break;
393
+ if (ancestor.type === "ArrayExpression") {
394
+ if (!isPromiseCombinatorArrayArg(ancestor) || isMapResultChain(current)) break;
395
+ }
396
+ if (ancestor.type === "SequenceExpression" && ancestor.expressions?.[ancestor.expressions.length - 1] !== current) break;
397
+ if (ancestor.type === "UnaryExpression" || ancestor.type === "BinaryExpression") break;
398
+ if (ancestor.type === "LogicalExpression" && ancestor.operator === "&&" && ancestor.left === current) break;
399
+ if (ancestor.type === "ConditionalExpression" && ancestor.test === current) break;
400
+ if (ancestor.type === "ExpressionStatement" || ancestor.type === "VariableDeclarator" || ancestor.type === "AssignmentExpression") break;
401
+ current = ancestor;
402
+ }
403
+ return false;
404
+ }
405
+ /**
406
+ * Whether `await ` can be safely inserted before a log call as an autofix.
407
+ * Only a standalone statement inside an async function qualifies: inserting
408
+ * `await` where the call's value is used (assigned, passed as an argument,
409
+ * returned) would change a `Promise<void>` into `void` and can break code that
410
+ * uses that promise.
411
+ */
412
+ function canInsertAwait(node) {
413
+ const enclosingFn = findEnclosingFunction(node);
414
+ return enclosingFn != null && enclosingFn.async === true && node.parent?.type === "ExpressionStatement";
415
+ }
416
+ /**
417
+ * Resolve the static name of an object property key, or `null` when the key is
418
+ * computed from a non-literal expression (e.g. `[someVar]`). A plain key
419
+ * (`loggers`), a string-literal key (`"loggers"`), and a computed
420
+ * string-literal key (`["loggers"]`) all resolve to their name, but a computed
421
+ * identifier key like `[loggers]` (a variable) does not.
422
+ */
423
+ function staticKeyName(prop) {
424
+ if (!prop.computed) {
425
+ if (typeof prop.key?.name === "string") return prop.key.name;
426
+ if (typeof prop.key?.value === "string") return prop.key.value;
427
+ return null;
428
+ }
429
+ if (prop.key?.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
430
+ if (prop.key?.type === "TemplateLiteral" && prop.key.expressions?.length === 0 && prop.key.quasis?.length === 1) {
431
+ const cooked = prop.key.quasis[0]?.value?.cooked ?? prop.key.quasis[0]?.cooked;
432
+ return typeof cooked === "string" ? cooked : null;
433
+ }
434
+ return null;
435
+ }
436
+ /**
437
+ * Whether an AST node is the string `value`, written either as a plain string
438
+ * literal or as a template literal with no interpolations (a backtick constant
439
+ * such as `` `logtape` ``). Deno's `TemplateElement` exposes `cooked`
440
+ * directly; ESTree nests it under `value.cooked`, so accept either shape.
441
+ */
442
+ function isStringLiteral(node, value) {
443
+ if (node?.type === "Literal") return node.value === value;
444
+ if (node?.type === "TemplateLiteral") {
445
+ const cooked = node.quasis?.[0]?.value?.cooked ?? node.quasis?.[0]?.cooked;
446
+ return node.expressions?.length === 0 && node.quasis?.length === 1 && cooked === value;
447
+ }
448
+ return false;
449
+ }
450
+ /**
451
+ * Whether an AST node is the array literal `["logtape"]` or
452
+ * `["logtape", "meta"]`.
453
+ */
454
+ function isLogtapeMetaArray(node) {
455
+ if (node?.type !== "ArrayExpression") return false;
456
+ const elems = node.elements;
457
+ if (!isStringLiteral(elems[0], "logtape")) return false;
458
+ if (elems.length === 1) return true;
459
+ return elems.length === 2 && isStringLiteral(elems[1], "meta");
460
+ }
461
+ /**
462
+ * Whether a logger entry covers the meta category with at least one non-empty
463
+ * sinks list. Only the array form (`["logtape"]` or `["logtape", "meta"]`)
464
+ * configures the meta logger: core's `configureInternal()` meta check inspects
465
+ * the category as an array, so a bare string `category: "logtape"` leaves the
466
+ * meta logger unconfigured and must not satisfy the rule.
467
+ */
468
+ function isMetaLoggerEntry(entry) {
469
+ if (!entry || entry.type !== "ObjectExpression") return false;
470
+ if (entry.properties?.some((p) => p.type === "SpreadElement")) return true;
471
+ let categoryNode = null;
472
+ let sinksNode = null;
473
+ for (const prop of entry.properties) {
474
+ if (prop.type !== "Property") continue;
475
+ const keyName = staticKeyName(prop);
476
+ if (keyName === "category") categoryNode = unwrapTypeAssertion(prop.value);
477
+ if (keyName === "sinks") sinksNode = unwrapTypeAssertion(prop.value);
478
+ }
479
+ if (!categoryNode) return false;
480
+ if (!isLogtapeMetaArray(categoryNode)) return false;
481
+ if (!sinksNode) return false;
482
+ if (sinksNode.type !== "ArrayExpression") return true;
483
+ return sinksNode.elements.length > 0;
484
+ }
485
+ /**
486
+ * Whether a `configure()`/`configureSync()` argument lacks a dedicated meta
487
+ * logger sink and so should be reported. Returns `false` (no report) when the
488
+ * argument is not an object literal, uses spread elements that may supply the
489
+ * meta logger, or already has a logger entry for the meta category.
490
+ */
491
+ function configNeedsMetaSink(configArg) {
492
+ if (!configArg || configArg.type !== "ObjectExpression") return false;
493
+ const properties = configArg.properties ?? [];
494
+ const loggersProperty = properties.find((p) => p.type === "Property" && staticKeyName(p) === "loggers");
495
+ const hasSpread = properties.some((p) => p.type === "SpreadElement");
496
+ if (!loggersProperty) return !hasSpread;
497
+ const loggersValue = unwrapTypeAssertion(loggersProperty.value);
498
+ if (loggersValue.type !== "ArrayExpression") return false;
499
+ const hasLoggerSpread = loggersValue.elements?.some((el) => el?.type === "SpreadElement");
500
+ if (hasLoggerSpread) return false;
501
+ return !loggersValue.elements?.some(isMetaLoggerEntry);
502
+ }
503
+
504
+ //#endregion
505
+ export { LOG_METHODS, canInsertAwait, configNeedsMetaSink, containsAwaitOrYield, isAsyncFunctionExpr, isLogPromiseHandled, isLogtapeImportSource, isPromiseReturningCallback, logMethodName, propsHaveEagerCall, selectLazyPropsObject, unwrapTypeAssertion };
506
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","names":["LOG_METHODS: Set<string>","ASYNC_FUNCTION_TYPES: Set<string>","source: unknown","node: any","callee: any","lazyNames: Set<string>","args: any","propsObject: any","prop: any","fn: any","DISCARDING_ARRAY_METHODS: Set<string>","FIRE_AND_FORGET_GLOBALS: Set<string>","ELEMENT_PRESERVING_ARRAY_METHODS: Set<string>","arrayNode: any","current: any","ancestor: any","grandAncestor: any","value: string","entry: any","p: any","categoryNode: any","sinksNode: any","configArg: any","el: any"],"sources":["../../src/core/ast.ts"],"sourcesContent":["/**\n * Shared, host-agnostic AST analysis used by both the ESLint rules\n * (`../rules/`) and the Deno Lint plugin (`../deno/plugin.ts`).\n *\n * Every function here works on plain ESTree-shaped AST nodes and depends only\n * on `node.type` / `node.parent` style traversal, so the same logic runs under\n * ESLint, Oxlint, and Deno Lint. Divergences between the host ASTs (e.g. Deno\n * exposing `TemplateElement.cooked` directly where ESTree nests it under\n * `value.cooked`) are absorbed here with `??` fallbacks, so an edge case is\n * fixed once rather than in two drifting copies.\n *\n * This module must not import from `eslint`, not even types: the Deno plugin\n * imports it and must stay loadable without the ESLint package present.\n *\n * @module\n */\n\n// The lint AST is untyped and differs across hosts, so these helpers operate\n// on `any` nodes by design.\n// deno-lint-ignore-file no-explicit-any\n\n/**\n * Log method names that the LogTape lint rules check.\n */\nexport const LOG_METHODS: Set<string> = new Set([\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"warning\",\n \"error\",\n \"fatal\",\n]);\n\n/**\n * AST node types that introduce their own function scope.\n */\nexport const ASYNC_FUNCTION_TYPES: Set<string> = new Set([\n \"ArrowFunctionExpression\",\n \"FunctionExpression\",\n \"FunctionDeclaration\",\n]);\n\n/**\n * Maximum depth for the recursive AST scans. The AST is finite, so this is a\n * safety net against pathological nesting rather than an expected limit; it is\n * generous enough to cover deeply nested log property objects (complex API\n * payloads, ORM entities) without false negatives.\n */\nconst MAX_RECURSION_DEPTH = 100;\n\n/**\n * Whether an import source refers to the LogTape core package. Accepts the\n * bare specifier (`@logtape/logtape`) as well as direct Deno-style `jsr:` and\n * `npm:` specifiers with an optional version suffix (e.g.\n * `jsr:@logtape/logtape` or `npm:@logtape/logtape@^1.0.0`).\n */\nexport function isLogtapeImportSource(source: unknown): boolean {\n return typeof source === \"string\" &&\n /^(?:(?:jsr|npm):)?@logtape\\/logtape(?:@[^/]+)?$/.test(source);\n}\n\n/**\n * Unwrap TypeScript type-assertion wrappers around an expression (`x as T`,\n * `<T>x`, `x satisfies T`, `x!`) so the rules analyze the underlying node.\n * Each wrapper exposes the inner node as `.expression`. Returns the node\n * unchanged when it is not such a wrapper.\n */\nexport function unwrapTypeAssertion(node: any): any {\n while (\n node &&\n (node.type === \"TSAsExpression\" ||\n node.type === \"TSTypeAssertion\" ||\n node.type === \"TSSatisfiesExpression\" ||\n node.type === \"TSNonNullExpression\")\n ) {\n node = node.expression;\n }\n return node;\n}\n\n/**\n * Resolve the log method name from a member-expression callee\n * (`logger.debug` -> `\"debug\"`), supporting computed string-literal access\n * (`logger[\"debug\"]`). Returns `null` for a computed non-literal property or a\n * non-member callee.\n */\nexport function logMethodName(callee: any): string | null {\n if (!callee || callee.type !== \"MemberExpression\") return null;\n if (!callee.computed) return callee.property?.name ?? null;\n // A computed key must be a string literal; a numeric literal (logger[0]) is\n // not a method name and must not be returned as one.\n return callee.property?.type === \"Literal\" &&\n typeof callee.property.value === \"string\"\n ? callee.property.value\n : null;\n}\n\n/**\n * Recursively check whether an AST node contains an eagerly evaluated call\n * anywhere in its subtree. Only inspects own-enumerable child nodes, skipping\n * metadata fields. A LogTape `lazy(...)` call (whose local names are in\n * `lazyNames`) is already deferred, so it is not counted as eager; its\n * arguments are still inspected, so `lazy(expensive())` is caught while\n * `lazy(() => expensive())` is not.\n */\nexport function containsCallExpression(\n node: any,\n lazyNames: Set<string>,\n depth = 0,\n): boolean {\n if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== \"object\") {\n return false;\n }\n const isLazyCall = node.type === \"CallExpression\" &&\n node.callee?.type === \"Identifier\" && lazyNames.has(node.callee.name);\n if (\n !isLazyCall && (\n node.type === \"CallExpression\" ||\n node.type === \"OptionalCallExpression\" ||\n node.type === \"NewExpression\" ||\n node.type === \"TaggedTemplateExpression\" ||\n node.type === \"ImportExpression\"\n )\n ) {\n return true;\n }\n // Don't recurse into function bodies — calls inside them are deferred,\n // not eagerly evaluated at the call site.\n if (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"FunctionDeclaration\"\n ) {\n return false;\n }\n // Enumerate with for-in, not Object.keys: Deno's lint AST exposes child\n // nodes as enumerable getters on the prototype (Object.keys returns []),\n // while ESTree exposes them as own properties; for-in covers both. `parent`\n // is non-enumerable in Deno and skipped below for ESTree.\n for (const key in node) {\n if (\n key === \"type\" || key === \"start\" || key === \"end\" ||\n key === \"loc\" || key === \"parent\" || key === \"range\"\n ) {\n continue;\n }\n const child = node[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (\n typeof item === \"object\" && item !== null &&\n containsCallExpression(item, lazyNames, depth + 1)\n ) return true;\n }\n } else if (typeof child === \"object\" && child !== null) {\n if (containsCallExpression(child, lazyNames, depth + 1)) return true;\n }\n }\n return false;\n}\n\n/**\n * Recursively check whether a node contains an `AwaitExpression` or\n * `YieldExpression` in the same function scope. Does not descend into nested\n * function bodies.\n */\nexport function containsAwaitOrYield(node: any, depth = 0): boolean {\n if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== \"object\") {\n return false;\n }\n if (node.type === \"AwaitExpression\" || node.type === \"YieldExpression\") {\n return true;\n }\n if (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"FunctionDeclaration\"\n ) {\n return false;\n }\n // Enumerate with for-in, not Object.keys: Deno's lint AST exposes child\n // nodes as enumerable getters on the prototype (Object.keys returns []),\n // while ESTree exposes them as own properties; for-in covers both. `parent`\n // is non-enumerable in Deno and skipped below for ESTree.\n for (const key in node) {\n if (\n key === \"type\" || key === \"start\" || key === \"end\" ||\n key === \"loc\" || key === \"parent\" || key === \"range\"\n ) {\n continue;\n }\n const child = node[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (\n typeof item === \"object\" && item !== null &&\n containsAwaitOrYield(item, depth + 1)\n ) return true;\n }\n } else if (typeof child === \"object\" && child !== null) {\n if (containsAwaitOrYield(child, depth + 1)) return true;\n }\n }\n return false;\n}\n\n/**\n * From a log call's argument list, select the eager properties object and note\n * whether it came from the properties-only overload. The properties object is\n * the second argument in the message+properties form\n * (`logger.debug(\"msg\", { ... })`) or the first argument in the\n * properties-only form (`logger.debug({ ... })`). TypeScript type assertions\n * (e.g. `{ ... } as const`) are unwrapped first. Returns `null` when neither\n * argument is an object literal.\n *\n * `propsObject` is the unwrapped object (for detection and the report\n * location); `fixTarget` is the original, still-wrapped argument node, so the\n * autofix replaces the whole `{ ... } as const` rather than leaving the\n * assertion dangling on the new callback. When the argument is not wrapped the\n * two are the same node.\n */\nexport function selectLazyPropsObject(\n args: any,\n): { propsObject: any; fixTarget: any; propertiesOnly: boolean } | null {\n const firstRaw = args?.[0];\n const secondRaw = args?.[1];\n const firstArg = unwrapTypeAssertion(firstRaw);\n const secondArg = unwrapTypeAssertion(secondRaw);\n if (firstArg && firstArg.type === \"ObjectExpression\") {\n return { propsObject: firstArg, fixTarget: firstRaw, propertiesOnly: true };\n }\n if (secondArg && secondArg.type === \"ObjectExpression\") {\n return {\n propsObject: secondArg,\n fixTarget: secondRaw,\n propertiesOnly: false,\n };\n }\n return null;\n}\n\n/**\n * Whether any property (or spread) of a properties object contains an eager\n * call that would benefit from lazy evaluation.\n */\nexport function propsHaveEagerCall(\n propsObject: any,\n lazyNames: Set<string>,\n): boolean {\n return propsObject.properties?.some(\n (prop: any) =>\n (prop.type === \"Property\" &&\n (containsCallExpression(prop.value, lazyNames) ||\n (prop.computed &&\n containsCallExpression(prop.key, lazyNames)))) ||\n (prop.type === \"SpreadElement\" &&\n containsCallExpression(prop.argument, lazyNames)),\n ) ?? false;\n}\n\n/**\n * Whether `node` is an async function literal (arrow or function expression).\n */\nexport function isAsyncFunctionExpr(node: any): boolean {\n return (node?.type === \"ArrowFunctionExpression\" ||\n node?.type === \"FunctionExpression\") && node.async === true;\n}\n\n/**\n * Walk up the parent chain to find the nearest enclosing function. Returns\n * `null` if the top of the tree is reached without finding one.\n */\nexport function findEnclosingFunction(node: any): any {\n let current = node.parent;\n while (current) {\n if (ASYNC_FUNCTION_TYPES.has(current.type)) return current;\n current = current.parent;\n }\n return null;\n}\n\n/**\n * Whether `fn` is a function passed directly as a call argument (e.g. an array\n * `.map()`/`.forEach()` callback). Such a function's return value is decided\n * by the receiving call, so a promise it returns may be awaited or discarded.\n */\nexport function isCallArgumentFunction(fn: any): boolean {\n const parent = fn?.parent;\n return parent?.type === \"CallExpression\" &&\n parent.callee !== fn &&\n (parent.arguments?.includes(fn) ?? false);\n}\n\n/**\n * Array iteration methods that ignore, coerce, or merely thread the callback's\n * return value, so a promise returned from the callback is not awaited per\n * call. `reduce`/`reduceRight` pass it on as the next accumulator (only the\n * final accumulator is the result), so earlier per-item promises are dropped.\n */\nconst DISCARDING_ARRAY_METHODS: Set<string> = new Set([\n \"forEach\",\n \"filter\",\n \"find\",\n \"findLast\",\n \"findIndex\",\n \"findLastIndex\",\n \"some\",\n \"every\",\n \"reduce\",\n \"reduceRight\",\n]);\n\n/**\n * Global \"fire and forget\" functions that ignore their callback's return value\n * entirely, so a promise returned from the callback is never awaited.\n */\nconst FIRE_AND_FORGET_GLOBALS: Set<string> = new Set([\n \"setTimeout\",\n \"setInterval\",\n \"setImmediate\",\n \"queueMicrotask\",\n \"requestAnimationFrame\",\n \"requestIdleCallback\",\n]);\n\n/**\n * Whether `fn` is a callback argument to a call that ignores or coerces its\n * callback's return value: an array method like `forEach`/`filter`/`some`, or a\n * fire-and-forget global like `setTimeout`/`queueMicrotask`. Such a call does\n * not propagate the callback's promise, so an async log returned from it is\n * dropped and the walk must stop there rather than continue to an outer\n * await/return.\n */\nexport function isDiscardedCallbackArgument(fn: any): boolean {\n const parent = fn?.parent;\n if (!parent || parent.type !== \"CallExpression\") return false;\n if (parent.callee === fn) return false;\n if (!(parent.arguments?.includes(fn) ?? false)) return false;\n const callee = parent.callee;\n // setTimeout(() => ...), queueMicrotask(() => ...), etc.\n if (\n callee?.type === \"Identifier\" && FIRE_AND_FORGET_GLOBALS.has(callee.name)\n ) {\n return true;\n }\n // items.forEach(() => ...) or items[\"forEach\"](() => ...), etc.\n // logMethodName resolves both the plain and computed string-literal forms.\n const methodName = logMethodName(callee);\n return methodName !== null && DISCARDING_ARRAY_METHODS.has(methodName);\n}\n\n/**\n * Whether `node` is the result of a `map()`/`flatMap()` call. Such a call\n * returns an array of the callback's return values, so awaiting or returning it\n * does not await the element promises; only a Promise combinator\n * (`Promise.all`/`allSettled`) awaits those.\n */\nexport function isMapResult(node: any): boolean {\n return node?.type === \"CallExpression\" &&\n node.callee?.type === \"MemberExpression\" &&\n !node.callee.computed &&\n node.callee.property?.type === \"Identifier\" &&\n (node.callee.property.name === \"map\" ||\n node.callee.property.name === \"flatMap\");\n}\n\n/**\n * Array methods that return a new array holding the same elements, so a\n * `map()`/`flatMap()` result chained through them is still an array of the\n * original promises (e.g. `arr.map(cb).filter(Boolean)`). Methods that unwrap\n * to a single element (`find`, `at`) or transform the elements (`map`,\n * `reduce`) are deliberately excluded.\n */\nconst ELEMENT_PRESERVING_ARRAY_METHODS: Set<string> = new Set([\n \"filter\",\n \"slice\",\n \"concat\",\n \"flat\",\n \"reverse\",\n \"sort\",\n \"toSorted\",\n \"toReversed\",\n \"with\",\n]);\n\n/**\n * Whether `node` evaluates to an array of the promises produced by a\n * `map()`/`flatMap()`, either directly or chained through element-preserving\n * array methods (`arr.map(cb).filter(...).slice(...)`). Awaiting or returning\n * such an array awaits the array, not the element promises.\n */\nexport function isMapResultChain(node: any, depth = 0): boolean {\n if (depth > MAX_RECURSION_DEPTH || !node) return false;\n if (isMapResult(node)) return true;\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"MemberExpression\" &&\n !node.callee.computed &&\n node.callee.property?.type === \"Identifier\" &&\n ELEMENT_PRESERVING_ARRAY_METHODS.has(node.callee.property.name)\n ) {\n return isMapResultChain(node.callee.object, depth + 1);\n }\n return false;\n}\n\n/**\n * Whether `node` is an expression that is syntactically a promise. Recognizes\n * `x.then(...)` / `.catch(...)` / `.finally(...)`, `new Promise(...)`, and\n * `Promise.resolve/reject/all/allSettled/race/any(...)`. This cannot see a\n * promise returned by an opaque call (e.g. `fetchData()` whose return type is a\n * promise), which a syntactic lint rule has no type information for.\n */\nexport function isPromiseReturningExpr(node: any): boolean {\n if (!node) return false;\n if (\n node.type === \"CallExpression\" &&\n node.callee?.type === \"MemberExpression\" && !node.callee.computed &&\n node.callee.property?.type === \"Identifier\"\n ) {\n const name = node.callee.property.name;\n if (name === \"then\" || name === \"catch\" || name === \"finally\") return true;\n if (\n node.callee.object?.type === \"Identifier\" &&\n node.callee.object.name === \"Promise\"\n ) return true;\n }\n if (\n node.type === \"NewExpression\" && node.callee?.type === \"Identifier\" &&\n node.callee.name === \"Promise\"\n ) return true;\n return false;\n}\n\n/**\n * Whether a block contains a `return <promise>` statement, without descending\n * into nested functions (whose returns belong to them, not to the block's\n * function).\n */\nexport function blockReturnsPromise(node: any, depth = 0): boolean {\n if (depth > MAX_RECURSION_DEPTH || !node || typeof node !== \"object\") {\n return false;\n }\n if (node.type === \"ReturnStatement\") {\n return isPromiseReturningExpr(node.argument);\n }\n if (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"FunctionDeclaration\"\n ) {\n return false;\n }\n // Enumerate with for-in, not Object.keys: Deno's lint AST exposes child\n // nodes as enumerable getters on the prototype (Object.keys returns []),\n // while ESTree exposes them as own properties; for-in covers both. `parent`\n // is non-enumerable in Deno and skipped below for ESTree.\n for (const key in node) {\n if (\n key === \"type\" || key === \"start\" || key === \"end\" ||\n key === \"loc\" || key === \"parent\" || key === \"range\"\n ) continue;\n const child = node[key];\n if (Array.isArray(child)) {\n for (const item of child) {\n if (\n typeof item === \"object\" && item !== null &&\n blockReturnsPromise(item, depth + 1)\n ) return true;\n }\n } else if (typeof child === \"object\" && child !== null) {\n if (blockReturnsPromise(child, depth + 1)) return true;\n }\n }\n return false;\n}\n\n/**\n * Whether `fn` is a function (arrow, function expression, or function\n * declaration) whose body returns a syntactically promise-typed value, even\n * though it is not declared `async`. LogTape awaits any promise the lazy\n * callback returns, e.g. `() => fetchData().then((data) => ({ data }))`, so a\n * non-async helper resolved by reference needs the same handling as an inline\n * one.\n */\nexport function isPromiseReturningCallback(fn: any): boolean {\n if (\n fn?.type !== \"ArrowFunctionExpression\" &&\n fn?.type !== \"FunctionExpression\" &&\n fn?.type !== \"FunctionDeclaration\"\n ) {\n return false;\n }\n const body = fn.body;\n // Concise arrow body: `() => promise`.\n if (body && body.type !== \"BlockStatement\") {\n return isPromiseReturningExpr(body);\n }\n return blockReturnsPromise(body);\n}\n\n/**\n * Whether `arrayNode` is the argument array of a Promise combinator that awaits\n * every element, i.e. `Promise.all([...])` or `Promise.allSettled([...])`.\n * Those consume all element promises, so a log promise inside the array is\n * still awaited when the combinator is awaited. `Promise.race`/`Promise.any`\n * are excluded: they can settle on another promise before the log promise\n * resolves, so the log write is not guaranteed to flush. A bare array literal,\n * by contrast, does not preserve promise semantics at all.\n */\nexport function isPromiseCombinatorArrayArg(arrayNode: any): boolean {\n const parent = arrayNode.parent;\n if (!parent || parent.type !== \"CallExpression\") return false;\n if (!(parent.arguments?.includes(arrayNode) ?? false)) return false;\n const callee = parent.callee;\n return callee?.type === \"MemberExpression\" &&\n !callee.computed &&\n callee.object?.type === \"Identifier\" &&\n callee.object.name === \"Promise\" &&\n callee.property?.type === \"Identifier\" &&\n [\"all\", \"allSettled\"].includes(callee.property.name);\n}\n\n/**\n * Walk the ancestor chain of a log call to decide whether the promise it\n * returns is awaited, returned, or otherwise propagated to a caller that can\n * await it. Returns `true` when the promise is handled and the call therefore\n * needs no `await`.\n *\n * Handled: the call is awaited, returned from a non-discarding function, the\n * concise body of a non-discarding arrow, or chained with\n * `.then`/`.catch`/`.finally`, or it sits inside `Promise.all`/`allSettled`.\n * Not handled (the walk stops and returns `false`): the promise is wrapped in\n * an object/array literal, awaiting a `map()`/`flatMap()` array (which awaits\n * the array, not its element promises), dropped by a discarding callback\n * (`forEach` etc.), or it reaches a statement boundary unconsumed.\n */\nexport function isLogPromiseHandled(node: any): boolean {\n let current: any = node;\n while (current) {\n const ancestor: any = current.parent;\n if (!ancestor) break;\n if (ancestor.type === \"AwaitExpression\") {\n // Awaiting a map()/flatMap() result (or such a result chained through\n // array methods) awaits the array, not the element promises inside it;\n // only a Promise combinator does.\n if (isMapResultChain(current)) break;\n return true;\n }\n // A return makes the promise the enclosing function's return value. If\n // that function is a discarded call argument (e.g. a forEach()/map()\n // callback), keep walking from it so the outer call decides whether the\n // promise is awaited or discarded; if it is a normal or stored function,\n // trust its caller and treat it as propagated.\n if (ancestor.type === \"ReturnStatement\") {\n // Returning a map()/flatMap() result (or one chained through array\n // methods) propagates the array, not the element promises.\n if (isMapResultChain(current)) break;\n const fn = findEnclosingFunction(ancestor);\n if (fn && isCallArgumentFunction(fn)) {\n // A discarder (forEach etc.) drops the returned promise, so the walk\n // must stop rather than reach an outer await/return.\n if (isDiscardedCallbackArgument(fn)) break;\n current = fn;\n continue;\n }\n return true;\n }\n // Concise arrow body: `() => logger.debug(...)` makes the promise the\n // arrow's return value, the same as a return statement. Apply the same\n // rule: keep walking when the arrow is a non-discarding callback, otherwise\n // treat it as propagated to an unknown caller (handled).\n if (\n ancestor.type === \"ArrowFunctionExpression\" &&\n ancestor.body === current\n ) {\n if (!isCallArgumentFunction(ancestor)) return true;\n // A discarder (forEach etc.) drops the promise; stop the walk.\n if (isDiscardedCallbackArgument(ancestor)) break;\n // Otherwise fall through and keep walking from the arrow itself.\n }\n // .then()/.catch()/.finally() must be actual non-computed calls. Computed\n // accesses like obj[then]() use a variable, not the method.\n if (ancestor.type === \"MemberExpression\") {\n const prop = ancestor.property;\n const isPromiseMethod = !ancestor.computed\n ? prop?.type === \"Identifier\" &&\n [\"then\", \"catch\", \"finally\"].includes(prop.name)\n : prop?.type === \"Literal\" &&\n [\"then\", \"catch\", \"finally\"].includes(prop.value);\n if (isPromiseMethod) {\n const grandAncestor: any = ancestor.parent;\n if (\n grandAncestor?.type === \"CallExpression\" &&\n grandAncestor.callee === ancestor\n ) {\n return true;\n }\n }\n }\n // An array or object literal wraps the promise; awaiting or returning the\n // container does not await or propagate the promise itself. The one\n // exception is an array passed to a Promise combinator (Promise.all etc.),\n // which consumes its elements.\n if (ancestor.type === \"ObjectExpression\") break;\n if (ancestor.type === \"ArrayExpression\") {\n // Break for a bare array. For a Promise.all/allSettled array, continue\n // only when the element we came through is itself a promise: if it is a\n // map()/flatMap() result (an array of promises), the combinator awaits\n // that array, not its inner promises, so the log promise stays unhandled.\n if (!isPromiseCombinatorArrayArg(ancestor) || isMapResultChain(current)) {\n break;\n }\n }\n // A sequence (comma) operator yields only its last operand; a log call in\n // an earlier position is evaluated for its side effect and its promise is\n // dropped.\n if (\n ancestor.type === \"SequenceExpression\" &&\n ancestor.expressions?.[ancestor.expressions.length - 1] !== current\n ) {\n break;\n }\n // A unary or binary operator consumes the promise as a plain value\n // (coercion, negation, etc.), so the awaited result is never the log\n // promise itself.\n if (\n ancestor.type === \"UnaryExpression\" ||\n ancestor.type === \"BinaryExpression\"\n ) {\n break;\n }\n // `a && b` yields its right operand when the left is truthy, and a promise\n // is always truthy, so a log promise in the left of && is discarded. (||\n // and ?? in the left position propagate it, so they are left handled.)\n if (\n ancestor.type === \"LogicalExpression\" &&\n ancestor.operator === \"&&\" &&\n ancestor.left === current\n ) {\n break;\n }\n // A conditional's test is coerced to a boolean and discarded; only the\n // taken branch propagates its value.\n if (\n ancestor.type === \"ConditionalExpression\" &&\n ancestor.test === current\n ) {\n break;\n }\n // Stop at statement boundaries.\n if (\n ancestor.type === \"ExpressionStatement\" ||\n ancestor.type === \"VariableDeclarator\" ||\n ancestor.type === \"AssignmentExpression\"\n ) {\n break;\n }\n current = ancestor;\n }\n return false;\n}\n\n/**\n * Whether `await ` can be safely inserted before a log call as an autofix.\n * Only a standalone statement inside an async function qualifies: inserting\n * `await` where the call's value is used (assigned, passed as an argument,\n * returned) would change a `Promise<void>` into `void` and can break code that\n * uses that promise.\n */\nexport function canInsertAwait(node: any): boolean {\n const enclosingFn = findEnclosingFunction(node);\n return enclosingFn != null && enclosingFn.async === true &&\n node.parent?.type === \"ExpressionStatement\";\n}\n\n/**\n * Resolve the static name of an object property key, or `null` when the key is\n * computed from a non-literal expression (e.g. `[someVar]`). A plain key\n * (`loggers`), a string-literal key (`\"loggers\"`), and a computed\n * string-literal key (`[\"loggers\"]`) all resolve to their name, but a computed\n * identifier key like `[loggers]` (a variable) does not.\n */\nexport function staticKeyName(prop: any): string | null {\n if (!prop.computed) {\n if (typeof prop.key?.name === \"string\") return prop.key.name;\n if (typeof prop.key?.value === \"string\") return prop.key.value;\n return null;\n }\n // A computed key must be a string literal or a no-interpolation template\n // literal ([`category`]); a numeric literal ({ [0]: ... }) is not a static\n // name.\n if (prop.key?.type === \"Literal\" && typeof prop.key.value === \"string\") {\n return prop.key.value;\n }\n if (\n prop.key?.type === \"TemplateLiteral\" &&\n prop.key.expressions?.length === 0 &&\n prop.key.quasis?.length === 1\n ) {\n const cooked = prop.key.quasis[0]?.value?.cooked ??\n prop.key.quasis[0]?.cooked;\n return typeof cooked === \"string\" ? cooked : null;\n }\n return null;\n}\n\n/**\n * Whether an AST node is the string `value`, written either as a plain string\n * literal or as a template literal with no interpolations (a backtick constant\n * such as `` `logtape` ``). Deno's `TemplateElement` exposes `cooked`\n * directly; ESTree nests it under `value.cooked`, so accept either shape.\n */\nexport function isStringLiteral(node: any, value: string): boolean {\n if (node?.type === \"Literal\") return node.value === value;\n if (node?.type === \"TemplateLiteral\") {\n const cooked = node.quasis?.[0]?.value?.cooked ?? node.quasis?.[0]?.cooked;\n return node.expressions?.length === 0 &&\n node.quasis?.length === 1 &&\n cooked === value;\n }\n return false;\n}\n\n/**\n * Whether an AST node is the array literal `[\"logtape\"]` or\n * `[\"logtape\", \"meta\"]`.\n */\nexport function isLogtapeMetaArray(node: any): boolean {\n if (node?.type !== \"ArrayExpression\") return false;\n const elems = node.elements;\n if (!isStringLiteral(elems[0], \"logtape\")) return false;\n if (elems.length === 1) return true;\n return elems.length === 2 && isStringLiteral(elems[1], \"meta\");\n}\n\n/**\n * Whether a logger entry covers the meta category with at least one non-empty\n * sinks list. Only the array form (`[\"logtape\"]` or `[\"logtape\", \"meta\"]`)\n * configures the meta logger: core's `configureInternal()` meta check inspects\n * the category as an array, so a bare string `category: \"logtape\"` leaves the\n * meta logger unconfigured and must not satisfy the rule.\n */\nexport function isMetaLoggerEntry(entry: any): boolean {\n if (!entry || entry.type !== \"ObjectExpression\") return false;\n\n // A spread element (e.g. { ...metaLogger }) makes the entry impossible to\n // analyze statically; assume it may configure the meta logger so the rule\n // does not warn on a config it cannot see into.\n if (entry.properties?.some((p: any) => p.type === \"SpreadElement\")) {\n return true;\n }\n\n let categoryNode: any = null;\n let sinksNode: any = null;\n\n for (const prop of entry.properties) {\n if (prop.type !== \"Property\") continue;\n const keyName = staticKeyName(prop);\n // Unwrap a type assertion on the value (e.g. `[\"logtape\", \"meta\"] as\n // const`) so an asserted category or sinks array is still recognized.\n if (keyName === \"category\") categoryNode = unwrapTypeAssertion(prop.value);\n if (keyName === \"sinks\") sinksNode = unwrapTypeAssertion(prop.value);\n }\n\n if (!categoryNode) return false;\n if (!isLogtapeMetaArray(categoryNode)) return false;\n\n if (!sinksNode) return false;\n // Non-literal (e.g. variable reference): assume non-empty to avoid false\n // positives.\n if (sinksNode.type !== \"ArrayExpression\") return true;\n return sinksNode.elements.length > 0;\n}\n\n/**\n * Whether a `configure()`/`configureSync()` argument lacks a dedicated meta\n * logger sink and so should be reported. Returns `false` (no report) when the\n * argument is not an object literal, uses spread elements that may supply the\n * meta logger, or already has a logger entry for the meta category.\n */\nexport function configNeedsMetaSink(configArg: any): boolean {\n if (!configArg || configArg.type !== \"ObjectExpression\") return false;\n\n const properties = configArg.properties ?? [];\n const loggersProperty = properties.find(\n (p: any) => p.type === \"Property\" && staticKeyName(p) === \"loggers\",\n );\n const hasSpread = properties.some((p: any) => p.type === \"SpreadElement\");\n if (!loggersProperty) return !hasSpread;\n\n const loggersValue = unwrapTypeAssertion(loggersProperty.value);\n if (loggersValue.type !== \"ArrayExpression\") return false;\n\n const hasLoggerSpread = loggersValue.elements?.some(\n (el: any) => el?.type === \"SpreadElement\",\n );\n if (hasLoggerSpread) return false;\n\n return !loggersValue.elements?.some(isMetaLoggerEntry);\n}\n"],"mappings":"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"}