@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,52 @@
1
+ const require_ast = require('../core/ast.cjs');
2
+ const require_utils = require('../utils.cjs');
3
+
4
+ //#region src/rules/no-message-interpolation.ts
5
+ /**
6
+ * ESLint rule that detects string template literals with `${}` interpolation
7
+ * passed as the message argument to LogTape log methods.
8
+ *
9
+ * Users should use message templates with structured properties instead:
10
+ *
11
+ * ```ts
12
+ * // Incorrect:
13
+ * logger.info(`User ${userId} logged in.`);
14
+ *
15
+ * // Correct:
16
+ * logger.info("User {userId} logged in.", { userId });
17
+ * ```
18
+ */
19
+ const noMessageInterpolation = {
20
+ meta: {
21
+ type: "problem",
22
+ docs: {
23
+ description: "Disallow string template literal interpolation in LogTape log message arguments",
24
+ recommended: true,
25
+ url: "https://logtape.org/lint/no-message-interpolation"
26
+ },
27
+ schema: [],
28
+ messages: { noInterpolation: "Avoid using template literal interpolation in log messages. Use a message template string with structured properties instead: logger.info(\"User {userId} logged in.\", { userId })." }
29
+ },
30
+ create(context) {
31
+ const scope = require_utils.createLogtapeScope(context);
32
+ return {
33
+ ImportDeclaration: scope.ImportDeclaration,
34
+ CallExpression(node) {
35
+ const { callee } = node;
36
+ if (!scope.isLogtapeCall(callee, node)) return;
37
+ const propertyName = require_ast.logMethodName(callee);
38
+ if (!propertyName || !require_ast.LOG_METHODS.has(propertyName)) return;
39
+ const firstArg = require_ast.unwrapTypeAssertion(node.arguments[0]);
40
+ if (!firstArg || firstArg.type !== "TemplateLiteral") return;
41
+ if (firstArg.expressions.length === 0) return;
42
+ context.report({
43
+ node: firstArg,
44
+ messageId: "noInterpolation"
45
+ });
46
+ }
47
+ };
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ exports.noMessageInterpolation = noMessageInterpolation;
@@ -0,0 +1,23 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/no-message-interpolation.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects string template literals with `${}` interpolation
7
+ * passed as the message argument to LogTape log methods.
8
+ *
9
+ * Users should use message templates with structured properties instead:
10
+ *
11
+ * ```ts
12
+ * // Incorrect:
13
+ * logger.info(`User ${userId} logged in.`);
14
+ *
15
+ * // Correct:
16
+ * logger.info("User {userId} logged in.", { userId });
17
+ * ```
18
+ */
19
+ declare const noMessageInterpolation: Rule.RuleModule;
20
+ //# sourceMappingURL=no-message-interpolation.d.ts.map
21
+ //#endregion
22
+ export { noMessageInterpolation };
23
+ //# sourceMappingURL=no-message-interpolation.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-message-interpolation.d.cts","names":[],"sources":["../../src/rules/no-message-interpolation.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBA;;;;;;;;;;;;cAAa,wBAAwB,IAAA,CAAK"}
@@ -0,0 +1,23 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/no-message-interpolation.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects string template literals with `${}` interpolation
7
+ * passed as the message argument to LogTape log methods.
8
+ *
9
+ * Users should use message templates with structured properties instead:
10
+ *
11
+ * ```ts
12
+ * // Incorrect:
13
+ * logger.info(`User ${userId} logged in.`);
14
+ *
15
+ * // Correct:
16
+ * logger.info("User {userId} logged in.", { userId });
17
+ * ```
18
+ */
19
+ declare const noMessageInterpolation: Rule.RuleModule;
20
+ //# sourceMappingURL=no-message-interpolation.d.ts.map
21
+ //#endregion
22
+ export { noMessageInterpolation };
23
+ //# sourceMappingURL=no-message-interpolation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-message-interpolation.d.ts","names":[],"sources":["../../src/rules/no-message-interpolation.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBA;;;;;;;;;;;;cAAa,wBAAwB,IAAA,CAAK"}
@@ -0,0 +1,53 @@
1
+ import { LOG_METHODS, logMethodName, unwrapTypeAssertion } from "../core/ast.js";
2
+ import { createLogtapeScope } from "../utils.js";
3
+
4
+ //#region src/rules/no-message-interpolation.ts
5
+ /**
6
+ * ESLint rule that detects string template literals with `${}` interpolation
7
+ * passed as the message argument to LogTape log methods.
8
+ *
9
+ * Users should use message templates with structured properties instead:
10
+ *
11
+ * ```ts
12
+ * // Incorrect:
13
+ * logger.info(`User ${userId} logged in.`);
14
+ *
15
+ * // Correct:
16
+ * logger.info("User {userId} logged in.", { userId });
17
+ * ```
18
+ */
19
+ const noMessageInterpolation = {
20
+ meta: {
21
+ type: "problem",
22
+ docs: {
23
+ description: "Disallow string template literal interpolation in LogTape log message arguments",
24
+ recommended: true,
25
+ url: "https://logtape.org/lint/no-message-interpolation"
26
+ },
27
+ schema: [],
28
+ messages: { noInterpolation: "Avoid using template literal interpolation in log messages. Use a message template string with structured properties instead: logger.info(\"User {userId} logged in.\", { userId })." }
29
+ },
30
+ create(context) {
31
+ const scope = createLogtapeScope(context);
32
+ return {
33
+ ImportDeclaration: scope.ImportDeclaration,
34
+ CallExpression(node) {
35
+ const { callee } = node;
36
+ if (!scope.isLogtapeCall(callee, node)) return;
37
+ const propertyName = logMethodName(callee);
38
+ if (!propertyName || !LOG_METHODS.has(propertyName)) return;
39
+ const firstArg = unwrapTypeAssertion(node.arguments[0]);
40
+ if (!firstArg || firstArg.type !== "TemplateLiteral") return;
41
+ if (firstArg.expressions.length === 0) return;
42
+ context.report({
43
+ node: firstArg,
44
+ messageId: "noInterpolation"
45
+ });
46
+ }
47
+ };
48
+ }
49
+ };
50
+
51
+ //#endregion
52
+ export { noMessageInterpolation };
53
+ //# sourceMappingURL=no-message-interpolation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-message-interpolation.js","names":["noMessageInterpolation: Rule.RuleModule"],"sources":["../../src/rules/no-message-interpolation.ts"],"sourcesContent":["import type { Rule } from \"eslint\";\nimport {\n LOG_METHODS,\n logMethodName,\n unwrapTypeAssertion,\n} from \"../core/ast.ts\";\nimport { createLogtapeScope } from \"../utils.ts\";\n\n/**\n * ESLint rule that detects string template literals with `${}` interpolation\n * passed as the message argument to LogTape log methods.\n *\n * Users should use message templates with structured properties instead:\n *\n * ```ts\n * // Incorrect:\n * logger.info(`User ${userId} logged in.`);\n *\n * // Correct:\n * logger.info(\"User {userId} logged in.\", { userId });\n * ```\n */\nexport const noMessageInterpolation: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Disallow string template literal interpolation in LogTape log message arguments\",\n recommended: true,\n url: \"https://logtape.org/lint/no-message-interpolation\",\n },\n schema: [],\n messages: {\n noInterpolation:\n \"Avoid using template literal interpolation in log messages. \" +\n \"Use a message template string with structured properties instead: \" +\n 'logger.info(\"User {userId} logged in.\", { userId }).',\n },\n },\n\n create(context) {\n const scope = createLogtapeScope(context);\n\n return {\n ImportDeclaration: scope.ImportDeclaration,\n CallExpression(node) {\n const { callee } = node;\n if (!scope.isLogtapeCall(callee, node)) return;\n const propertyName = logMethodName(callee);\n if (!propertyName || !LOG_METHODS.has(propertyName)) return;\n\n const firstArg = unwrapTypeAssertion(node.arguments[0]);\n if (!firstArg || firstArg.type !== \"TemplateLiteral\") return;\n if (firstArg.expressions.length === 0) return;\n\n context.report({\n node: firstArg,\n messageId: \"noInterpolation\",\n });\n },\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsBA,MAAaA,yBAA0C;CACrD,MAAM;EACJ,MAAM;EACN,MAAM;GACJ,aACE;GACF,aAAa;GACb,KAAK;EACN;EACD,QAAQ,CAAE;EACV,UAAU,EACR,iBACE,uLAGH;CACF;CAED,OAAO,SAAS;EACd,MAAM,QAAQ,mBAAmB,QAAQ;AAEzC,SAAO;GACL,mBAAmB,MAAM;GACzB,eAAe,MAAM;IACnB,MAAM,EAAE,QAAQ,GAAG;AACnB,SAAK,MAAM,cAAc,QAAQ,KAAK,CAAE;IACxC,MAAM,eAAe,cAAc,OAAO;AAC1C,SAAK,iBAAiB,YAAY,IAAI,aAAa,CAAE;IAErD,MAAM,WAAW,oBAAoB,KAAK,UAAU,GAAG;AACvD,SAAK,YAAY,SAAS,SAAS,kBAAmB;AACtD,QAAI,SAAS,YAAY,WAAW,EAAG;AAEvC,YAAQ,OAAO;KACb,MAAM;KACN,WAAW;IACZ,EAAC;GACH;EACF;CACF;AACF"}
@@ -0,0 +1,67 @@
1
+ const require_ast = require('../core/ast.cjs');
2
+ const require_utils = require('../utils.cjs');
3
+
4
+ //#region src/rules/no-unawaited-log.ts
5
+ /**
6
+ * ESLint rule that detects async lazy callbacks passed to LogTape log methods
7
+ * without `await`. The resulting `Promise<void>` would be silently ignored.
8
+ *
9
+ * ```ts
10
+ * // Incorrect:
11
+ * logger.debug("msg", async () => ({ data: await fetchData() }));
12
+ *
13
+ * // Correct:
14
+ * await logger.debug("msg", async () => ({ data: await fetchData() }));
15
+ * ```
16
+ */
17
+ const noUnawaitedLog = {
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ description: "Require await on LogTape log calls that use async lazy callbacks",
22
+ recommended: true,
23
+ url: "https://logtape.org/lint/no-unawaited-log"
24
+ },
25
+ fixable: "code",
26
+ schema: [],
27
+ messages: { requireAwait: "Async lazy callbacks must be awaited to ensure the log is flushed: await logger.debug(\"msg\", async () => ({ ... }))." }
28
+ },
29
+ create(context) {
30
+ const scope = require_utils.createLogtapeScope(context);
31
+ function resolvesToPromiseCallback(idNode, callNode) {
32
+ let cur = context.sourceCode?.getScope?.(callNode) ?? context.getScope?.();
33
+ while (cur) {
34
+ const variable = cur.set?.get(idNode.name);
35
+ if (variable) return variable.defs?.some((d) => {
36
+ if (d.type === "Variable") return require_ast.isAsyncFunctionExpr(d.node?.init) || require_ast.isPromiseReturningCallback(d.node?.init);
37
+ if (d.type === "FunctionName") return d.node?.async === true || require_ast.isPromiseReturningCallback(d.node);
38
+ return false;
39
+ }) ?? false;
40
+ cur = cur.upper;
41
+ }
42
+ return false;
43
+ }
44
+ return {
45
+ ImportDeclaration: scope.ImportDeclaration,
46
+ CallExpression(node) {
47
+ const { callee } = node;
48
+ if (!scope.isLogtapeCall(callee, node)) return;
49
+ const propertyName = require_ast.logMethodName(callee);
50
+ if (!propertyName || !require_ast.LOG_METHODS.has(propertyName)) return;
51
+ const secondArg = require_ast.unwrapTypeAssertion(node.arguments[1]);
52
+ if (!secondArg) return;
53
+ const isAsyncCallback = require_ast.isAsyncFunctionExpr(secondArg) || require_ast.isPromiseReturningCallback(secondArg) || secondArg.type === "Identifier" && resolvesToPromiseCallback(secondArg, node);
54
+ if (!isAsyncCallback) return;
55
+ if (require_ast.isLogPromiseHandled(node)) return;
56
+ context.report({
57
+ node,
58
+ messageId: "requireAwait",
59
+ fix: require_ast.canInsertAwait(node) ? (fixer) => fixer.insertTextBefore(node, "await ") : void 0
60
+ });
61
+ }
62
+ };
63
+ }
64
+ };
65
+
66
+ //#endregion
67
+ exports.noUnawaitedLog = noUnawaitedLog;
@@ -0,0 +1,21 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/no-unawaited-log.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects async lazy callbacks passed to LogTape log methods
7
+ * without `await`. The resulting `Promise<void>` would be silently ignored.
8
+ *
9
+ * ```ts
10
+ * // Incorrect:
11
+ * logger.debug("msg", async () => ({ data: await fetchData() }));
12
+ *
13
+ * // Correct:
14
+ * await logger.debug("msg", async () => ({ data: await fetchData() }));
15
+ * ```
16
+ */
17
+ declare const noUnawaitedLog: Rule.RuleModule;
18
+ //# sourceMappingURL=no-unawaited-log.d.ts.map
19
+ //#endregion
20
+ export { noUnawaitedLog };
21
+ //# sourceMappingURL=no-unawaited-log.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unawaited-log.d.cts","names":[],"sources":["../../src/rules/no-unawaited-log.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBA;;;;;;;;;;cAAa,gBAAgB,IAAA,CAAK"}
@@ -0,0 +1,21 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/no-unawaited-log.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects async lazy callbacks passed to LogTape log methods
7
+ * without `await`. The resulting `Promise<void>` would be silently ignored.
8
+ *
9
+ * ```ts
10
+ * // Incorrect:
11
+ * logger.debug("msg", async () => ({ data: await fetchData() }));
12
+ *
13
+ * // Correct:
14
+ * await logger.debug("msg", async () => ({ data: await fetchData() }));
15
+ * ```
16
+ */
17
+ declare const noUnawaitedLog: Rule.RuleModule;
18
+ //# sourceMappingURL=no-unawaited-log.d.ts.map
19
+ //#endregion
20
+ export { noUnawaitedLog };
21
+ //# sourceMappingURL=no-unawaited-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unawaited-log.d.ts","names":[],"sources":["../../src/rules/no-unawaited-log.ts"],"sourcesContent":[],"mappings":";;;;;;AAwBA;;;;;;;;;;cAAa,gBAAgB,IAAA,CAAK"}
@@ -0,0 +1,68 @@
1
+ import { LOG_METHODS, canInsertAwait, isAsyncFunctionExpr, isLogPromiseHandled, isPromiseReturningCallback, logMethodName, unwrapTypeAssertion } from "../core/ast.js";
2
+ import { createLogtapeScope } from "../utils.js";
3
+
4
+ //#region src/rules/no-unawaited-log.ts
5
+ /**
6
+ * ESLint rule that detects async lazy callbacks passed to LogTape log methods
7
+ * without `await`. The resulting `Promise<void>` would be silently ignored.
8
+ *
9
+ * ```ts
10
+ * // Incorrect:
11
+ * logger.debug("msg", async () => ({ data: await fetchData() }));
12
+ *
13
+ * // Correct:
14
+ * await logger.debug("msg", async () => ({ data: await fetchData() }));
15
+ * ```
16
+ */
17
+ const noUnawaitedLog = {
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ description: "Require await on LogTape log calls that use async lazy callbacks",
22
+ recommended: true,
23
+ url: "https://logtape.org/lint/no-unawaited-log"
24
+ },
25
+ fixable: "code",
26
+ schema: [],
27
+ messages: { requireAwait: "Async lazy callbacks must be awaited to ensure the log is flushed: await logger.debug(\"msg\", async () => ({ ... }))." }
28
+ },
29
+ create(context) {
30
+ const scope = createLogtapeScope(context);
31
+ function resolvesToPromiseCallback(idNode, callNode) {
32
+ let cur = context.sourceCode?.getScope?.(callNode) ?? context.getScope?.();
33
+ while (cur) {
34
+ const variable = cur.set?.get(idNode.name);
35
+ if (variable) return variable.defs?.some((d) => {
36
+ if (d.type === "Variable") return isAsyncFunctionExpr(d.node?.init) || isPromiseReturningCallback(d.node?.init);
37
+ if (d.type === "FunctionName") return d.node?.async === true || isPromiseReturningCallback(d.node);
38
+ return false;
39
+ }) ?? false;
40
+ cur = cur.upper;
41
+ }
42
+ return false;
43
+ }
44
+ return {
45
+ ImportDeclaration: scope.ImportDeclaration,
46
+ CallExpression(node) {
47
+ const { callee } = node;
48
+ if (!scope.isLogtapeCall(callee, node)) return;
49
+ const propertyName = logMethodName(callee);
50
+ if (!propertyName || !LOG_METHODS.has(propertyName)) return;
51
+ const secondArg = unwrapTypeAssertion(node.arguments[1]);
52
+ if (!secondArg) return;
53
+ const isAsyncCallback = isAsyncFunctionExpr(secondArg) || isPromiseReturningCallback(secondArg) || secondArg.type === "Identifier" && resolvesToPromiseCallback(secondArg, node);
54
+ if (!isAsyncCallback) return;
55
+ if (isLogPromiseHandled(node)) return;
56
+ context.report({
57
+ node,
58
+ messageId: "requireAwait",
59
+ fix: canInsertAwait(node) ? (fixer) => fixer.insertTextBefore(node, "await ") : void 0
60
+ });
61
+ }
62
+ };
63
+ }
64
+ };
65
+
66
+ //#endregion
67
+ export { noUnawaitedLog };
68
+ //# sourceMappingURL=no-unawaited-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-unawaited-log.js","names":["noUnawaitedLog: Rule.RuleModule","idNode: any","callNode: any","cur: any","d: any"],"sources":["../../src/rules/no-unawaited-log.ts"],"sourcesContent":["import type { Rule } from \"eslint\";\nimport {\n canInsertAwait,\n isAsyncFunctionExpr,\n isLogPromiseHandled,\n isPromiseReturningCallback,\n LOG_METHODS,\n logMethodName,\n unwrapTypeAssertion,\n} from \"../core/ast.ts\";\nimport { createLogtapeScope } from \"../utils.ts\";\n\n/**\n * ESLint rule that detects async lazy callbacks passed to LogTape log methods\n * without `await`. The resulting `Promise<void>` would be silently ignored.\n *\n * ```ts\n * // Incorrect:\n * logger.debug(\"msg\", async () => ({ data: await fetchData() }));\n *\n * // Correct:\n * await logger.debug(\"msg\", async () => ({ data: await fetchData() }));\n * ```\n */\nexport const noUnawaitedLog: Rule.RuleModule = {\n meta: {\n type: \"problem\",\n docs: {\n description:\n \"Require await on LogTape log calls that use async lazy callbacks\",\n recommended: true,\n url: \"https://logtape.org/lint/no-unawaited-log\",\n },\n fixable: \"code\",\n schema: [],\n messages: {\n requireAwait:\n \"Async lazy callbacks must be awaited to ensure the log is flushed: \" +\n 'await logger.debug(\"msg\", async () => ({ ... })).',\n },\n },\n\n create(context) {\n const scope = createLogtapeScope(context);\n\n // Does the identifier argument resolve to a binding whose value yields a\n // promise: an async function, or a non-async function that syntactically\n // returns one? This catches a callback passed by reference in both the\n // variable form (`const props = async () => ({ ... })` or\n // `const props = () => fetchData().then(...)`) and the function-declaration\n // form (`async function props() {}` or `function props() { return p.then(...) }`).\n // deno-lint-ignore no-explicit-any\n function resolvesToPromiseCallback(idNode: any, callNode: any): boolean {\n // deno-lint-ignore no-explicit-any\n let cur: any = (context as any).sourceCode?.getScope?.(callNode) ??\n // deno-lint-ignore no-explicit-any\n (context as any).getScope?.();\n while (cur) {\n const variable = cur.set?.get(idNode.name);\n if (variable) {\n // deno-lint-ignore no-explicit-any\n return variable.defs?.some((d: any) => {\n if (d.type === \"Variable\") {\n return isAsyncFunctionExpr(d.node?.init) ||\n isPromiseReturningCallback(d.node?.init);\n }\n // function props() {} is a FunctionName def.\n if (d.type === \"FunctionName\") {\n return d.node?.async === true ||\n isPromiseReturningCallback(d.node);\n }\n return false;\n }) ?? false;\n }\n cur = cur.upper;\n }\n return false;\n }\n\n return {\n ImportDeclaration: scope.ImportDeclaration,\n CallExpression(node) {\n const { callee } = node;\n if (!scope.isLogtapeCall(callee, node)) return;\n const propertyName = logMethodName(callee);\n if (!propertyName || !LOG_METHODS.has(propertyName)) return;\n\n const secondArg = unwrapTypeAssertion(node.arguments[1]);\n if (!secondArg) return;\n\n const isAsyncCallback = isAsyncFunctionExpr(secondArg) ||\n isPromiseReturningCallback(secondArg) ||\n (secondArg.type === \"Identifier\" &&\n resolvesToPromiseCallback(secondArg, node));\n if (!isAsyncCallback) return;\n\n // Walk the ancestor chain to check if the promise is handled (awaited,\n // returned, chained with .then(), or consumed by Promise.all()).\n if (isLogPromiseHandled(node)) return;\n\n context.report({\n node,\n messageId: \"requireAwait\",\n // Only autofix a standalone statement. Inserting `await` where the\n // call's value is used (assigned, passed as an argument, returned)\n // would change a Promise<void> into void and can break code that\n // uses that promise, so those contexts are reported without a fix.\n fix: canInsertAwait(node)\n ? (fixer) => fixer.insertTextBefore(node, \"await \")\n : undefined,\n });\n },\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAwBA,MAAaA,iBAAkC;CAC7C,MAAM;EACJ,MAAM;EACN,MAAM;GACJ,aACE;GACF,aAAa;GACb,KAAK;EACN;EACD,SAAS;EACT,QAAQ,CAAE;EACV,UAAU,EACR,cACE,yHAEH;CACF;CAED,OAAO,SAAS;EACd,MAAM,QAAQ,mBAAmB,QAAQ;EASzC,SAAS,0BAA0BC,QAAaC,UAAwB;GAEtE,IAAIC,MAAW,AAAC,QAAgB,YAAY,WAAW,SAAS,IAE9D,AAAC,QAAgB,YAAY;AAC/B,UAAO,KAAK;IACV,MAAM,WAAW,IAAI,KAAK,IAAI,OAAO,KAAK;AAC1C,QAAI,SAEF,QAAO,SAAS,MAAM,KAAK,CAACC,MAAW;AACrC,SAAI,EAAE,SAAS,WACb,QAAO,oBAAoB,EAAE,MAAM,KAAK,IACtC,2BAA2B,EAAE,MAAM,KAAK;AAG5C,SAAI,EAAE,SAAS,eACb,QAAO,EAAE,MAAM,UAAU,QACvB,2BAA2B,EAAE,KAAK;AAEtC,YAAO;IACR,EAAC,IAAI;AAER,UAAM,IAAI;GACX;AACD,UAAO;EACR;AAED,SAAO;GACL,mBAAmB,MAAM;GACzB,eAAe,MAAM;IACnB,MAAM,EAAE,QAAQ,GAAG;AACnB,SAAK,MAAM,cAAc,QAAQ,KAAK,CAAE;IACxC,MAAM,eAAe,cAAc,OAAO;AAC1C,SAAK,iBAAiB,YAAY,IAAI,aAAa,CAAE;IAErD,MAAM,YAAY,oBAAoB,KAAK,UAAU,GAAG;AACxD,SAAK,UAAW;IAEhB,MAAM,kBAAkB,oBAAoB,UAAU,IACpD,2BAA2B,UAAU,IACpC,UAAU,SAAS,gBAClB,0BAA0B,WAAW,KAAK;AAC9C,SAAK,gBAAiB;AAItB,QAAI,oBAAoB,KAAK,CAAE;AAE/B,YAAQ,OAAO;KACb;KACA,WAAW;KAKX,KAAK,eAAe,KAAK,GACrB,CAAC,UAAU,MAAM,iBAAiB,MAAM,SAAS;IAEtD,EAAC;GACH;EACF;CACF;AACF"}
@@ -0,0 +1,59 @@
1
+ const require_ast = require('../core/ast.cjs');
2
+ const require_utils = require('../utils.cjs');
3
+
4
+ //#region src/rules/prefer-lazy-evaluation.ts
5
+ /**
6
+ * ESLint rule that detects computed expressions (function calls) inside the
7
+ * properties object passed to LogTape log methods. Wrapping in a lazy
8
+ * callback prevents unnecessary computation when the log level is disabled.
9
+ *
10
+ * ```ts
11
+ * // Incorrect:
12
+ * logger.debug("User data: {userData}.", { userData: fetchUserData(userId) });
13
+ *
14
+ * // Correct:
15
+ * logger.debug("User data: {userData}.", () => ({ userData: fetchUserData(userId) }));
16
+ * ```
17
+ */
18
+ const preferLazyEvaluation = {
19
+ meta: {
20
+ type: "suggestion",
21
+ docs: {
22
+ description: "Prefer lazy evaluation callbacks over eager property objects in LogTape log calls",
23
+ recommended: true,
24
+ url: "https://logtape.org/lint/prefer-lazy-evaluation"
25
+ },
26
+ fixable: "code",
27
+ schema: [],
28
+ messages: { preferLazy: "Wrap the properties object in a lazy callback to avoid unnecessary computation: logger.debug(\"msg\", () => ({ ... }))." }
29
+ },
30
+ create(context) {
31
+ const scope = require_utils.createLogtapeScope(context);
32
+ return {
33
+ ImportDeclaration: scope.ImportDeclaration,
34
+ CallExpression(node) {
35
+ const { callee } = node;
36
+ if (!scope.isLogtapeCall(callee, node)) return;
37
+ const propertyName = require_ast.logMethodName(callee);
38
+ if (!propertyName || !require_ast.LOG_METHODS.has(propertyName)) return;
39
+ const selected = require_ast.selectLazyPropsObject(node.arguments);
40
+ if (!selected) return;
41
+ const { propsObject, fixTarget, propertiesOnly } = selected;
42
+ if (!require_ast.propsHaveEagerCall(propsObject, scope.effectiveLazyNames(node))) return;
43
+ const sourceCode = require_utils.getSourceCode(context);
44
+ const hasAsyncSyntax = require_ast.containsAwaitOrYield(propsObject);
45
+ context.report({
46
+ node: propsObject,
47
+ messageId: "preferLazy",
48
+ fix: hasAsyncSyntax ? void 0 : (fixer) => {
49
+ const objectText = sourceCode.getText(fixTarget);
50
+ return fixer.replaceText(fixTarget, propertiesOnly ? `"{*}", () => (${objectText})` : `() => (${objectText})`);
51
+ }
52
+ });
53
+ }
54
+ };
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ exports.preferLazyEvaluation = preferLazyEvaluation;
@@ -0,0 +1,22 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/prefer-lazy-evaluation.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects computed expressions (function calls) inside the
7
+ * properties object passed to LogTape log methods. Wrapping in a lazy
8
+ * callback prevents unnecessary computation when the log level is disabled.
9
+ *
10
+ * ```ts
11
+ * // Incorrect:
12
+ * logger.debug("User data: {userData}.", { userData: fetchUserData(userId) });
13
+ *
14
+ * // Correct:
15
+ * logger.debug("User data: {userData}.", () => ({ userData: fetchUserData(userId) }));
16
+ * ```
17
+ */
18
+ declare const preferLazyEvaluation: Rule.RuleModule;
19
+ //# sourceMappingURL=prefer-lazy-evaluation.d.ts.map
20
+ //#endregion
21
+ export { preferLazyEvaluation };
22
+ //# sourceMappingURL=prefer-lazy-evaluation.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-lazy-evaluation.d.cts","names":[],"sources":["../../src/rules/prefer-lazy-evaluation.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;;;;;;cAAa,sBAAsB,IAAA,CAAK"}
@@ -0,0 +1,22 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/prefer-lazy-evaluation.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects computed expressions (function calls) inside the
7
+ * properties object passed to LogTape log methods. Wrapping in a lazy
8
+ * callback prevents unnecessary computation when the log level is disabled.
9
+ *
10
+ * ```ts
11
+ * // Incorrect:
12
+ * logger.debug("User data: {userData}.", { userData: fetchUserData(userId) });
13
+ *
14
+ * // Correct:
15
+ * logger.debug("User data: {userData}.", () => ({ userData: fetchUserData(userId) }));
16
+ * ```
17
+ */
18
+ declare const preferLazyEvaluation: Rule.RuleModule;
19
+ //# sourceMappingURL=prefer-lazy-evaluation.d.ts.map
20
+ //#endregion
21
+ export { preferLazyEvaluation };
22
+ //# sourceMappingURL=prefer-lazy-evaluation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-lazy-evaluation.d.ts","names":[],"sources":["../../src/rules/prefer-lazy-evaluation.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;;;;;;cAAa,sBAAsB,IAAA,CAAK"}
@@ -0,0 +1,60 @@
1
+ import { LOG_METHODS, containsAwaitOrYield, logMethodName, propsHaveEagerCall, selectLazyPropsObject } from "../core/ast.js";
2
+ import { createLogtapeScope, getSourceCode } from "../utils.js";
3
+
4
+ //#region src/rules/prefer-lazy-evaluation.ts
5
+ /**
6
+ * ESLint rule that detects computed expressions (function calls) inside the
7
+ * properties object passed to LogTape log methods. Wrapping in a lazy
8
+ * callback prevents unnecessary computation when the log level is disabled.
9
+ *
10
+ * ```ts
11
+ * // Incorrect:
12
+ * logger.debug("User data: {userData}.", { userData: fetchUserData(userId) });
13
+ *
14
+ * // Correct:
15
+ * logger.debug("User data: {userData}.", () => ({ userData: fetchUserData(userId) }));
16
+ * ```
17
+ */
18
+ const preferLazyEvaluation = {
19
+ meta: {
20
+ type: "suggestion",
21
+ docs: {
22
+ description: "Prefer lazy evaluation callbacks over eager property objects in LogTape log calls",
23
+ recommended: true,
24
+ url: "https://logtape.org/lint/prefer-lazy-evaluation"
25
+ },
26
+ fixable: "code",
27
+ schema: [],
28
+ messages: { preferLazy: "Wrap the properties object in a lazy callback to avoid unnecessary computation: logger.debug(\"msg\", () => ({ ... }))." }
29
+ },
30
+ create(context) {
31
+ const scope = createLogtapeScope(context);
32
+ return {
33
+ ImportDeclaration: scope.ImportDeclaration,
34
+ CallExpression(node) {
35
+ const { callee } = node;
36
+ if (!scope.isLogtapeCall(callee, node)) return;
37
+ const propertyName = logMethodName(callee);
38
+ if (!propertyName || !LOG_METHODS.has(propertyName)) return;
39
+ const selected = selectLazyPropsObject(node.arguments);
40
+ if (!selected) return;
41
+ const { propsObject, fixTarget, propertiesOnly } = selected;
42
+ if (!propsHaveEagerCall(propsObject, scope.effectiveLazyNames(node))) return;
43
+ const sourceCode = getSourceCode(context);
44
+ const hasAsyncSyntax = containsAwaitOrYield(propsObject);
45
+ context.report({
46
+ node: propsObject,
47
+ messageId: "preferLazy",
48
+ fix: hasAsyncSyntax ? void 0 : (fixer) => {
49
+ const objectText = sourceCode.getText(fixTarget);
50
+ return fixer.replaceText(fixTarget, propertiesOnly ? `"{*}", () => (${objectText})` : `() => (${objectText})`);
51
+ }
52
+ });
53
+ }
54
+ };
55
+ }
56
+ };
57
+
58
+ //#endregion
59
+ export { preferLazyEvaluation };
60
+ //# sourceMappingURL=prefer-lazy-evaluation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-lazy-evaluation.js","names":["preferLazyEvaluation: Rule.RuleModule"],"sources":["../../src/rules/prefer-lazy-evaluation.ts"],"sourcesContent":["import type { Rule } from \"eslint\";\nimport {\n containsAwaitOrYield,\n LOG_METHODS,\n logMethodName,\n propsHaveEagerCall,\n selectLazyPropsObject,\n} from \"../core/ast.ts\";\nimport { createLogtapeScope, getSourceCode } from \"../utils.ts\";\n\n/**\n * ESLint rule that detects computed expressions (function calls) inside the\n * properties object passed to LogTape log methods. Wrapping in a lazy\n * callback prevents unnecessary computation when the log level is disabled.\n *\n * ```ts\n * // Incorrect:\n * logger.debug(\"User data: {userData}.\", { userData: fetchUserData(userId) });\n *\n * // Correct:\n * logger.debug(\"User data: {userData}.\", () => ({ userData: fetchUserData(userId) }));\n * ```\n */\nexport const preferLazyEvaluation: Rule.RuleModule = {\n meta: {\n type: \"suggestion\",\n docs: {\n description:\n \"Prefer lazy evaluation callbacks over eager property objects in LogTape log calls\",\n recommended: true,\n url: \"https://logtape.org/lint/prefer-lazy-evaluation\",\n },\n fixable: \"code\",\n schema: [],\n messages: {\n preferLazy:\n \"Wrap the properties object in a lazy callback to avoid unnecessary computation: \" +\n 'logger.debug(\"msg\", () => ({ ... })).',\n },\n },\n\n create(context) {\n const scope = createLogtapeScope(context);\n\n return {\n ImportDeclaration: scope.ImportDeclaration,\n CallExpression(node) {\n const { callee } = node;\n if (!scope.isLogtapeCall(callee, node)) return;\n const propertyName = logMethodName(callee);\n if (!propertyName || !LOG_METHODS.has(propertyName)) return;\n\n // The eager properties object is the second argument in the\n // message+properties form, logger.debug(\"msg\", { ... }), or the first\n // argument in the properties-only form, logger.debug({ ... }).\n const selected = selectLazyPropsObject(node.arguments);\n if (!selected) return;\n const { propsObject, fixTarget, propertiesOnly } = selected;\n\n // Use the lazy names that still resolve to the import at this call, so\n // a shadowed local `lazy(...)` is treated as an ordinary eager call.\n if (!propsHaveEagerCall(propsObject, scope.effectiveLazyNames(node))) {\n return;\n }\n\n const sourceCode = getSourceCode(context);\n const hasAsyncSyntax = containsAwaitOrYield(propsObject);\n context.report({\n node: propsObject,\n messageId: \"preferLazy\",\n // Wrap the whole argument (fixTarget), including any `as const` /\n // `satisfies` wrapper, so the assertion ends up inside the callback\n // instead of dangling on it.\n fix: hasAsyncSyntax ? undefined : (fixer) => {\n const objectText = sourceCode.getText(fixTarget);\n // The properties-only overload needs the \"{*}\" message inserted so\n // the lazy callback is still read as properties, not as a message.\n return fixer.replaceText(\n fixTarget,\n propertiesOnly\n ? `\"{*}\", () => (${objectText})`\n : `() => (${objectText})`,\n );\n },\n });\n },\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,MAAaA,uBAAwC;CACnD,MAAM;EACJ,MAAM;EACN,MAAM;GACJ,aACE;GACF,aAAa;GACb,KAAK;EACN;EACD,SAAS;EACT,QAAQ,CAAE;EACV,UAAU,EACR,YACE,0HAEH;CACF;CAED,OAAO,SAAS;EACd,MAAM,QAAQ,mBAAmB,QAAQ;AAEzC,SAAO;GACL,mBAAmB,MAAM;GACzB,eAAe,MAAM;IACnB,MAAM,EAAE,QAAQ,GAAG;AACnB,SAAK,MAAM,cAAc,QAAQ,KAAK,CAAE;IACxC,MAAM,eAAe,cAAc,OAAO;AAC1C,SAAK,iBAAiB,YAAY,IAAI,aAAa,CAAE;IAKrD,MAAM,WAAW,sBAAsB,KAAK,UAAU;AACtD,SAAK,SAAU;IACf,MAAM,EAAE,aAAa,WAAW,gBAAgB,GAAG;AAInD,SAAK,mBAAmB,aAAa,MAAM,mBAAmB,KAAK,CAAC,CAClE;IAGF,MAAM,aAAa,cAAc,QAAQ;IACzC,MAAM,iBAAiB,qBAAqB,YAAY;AACxD,YAAQ,OAAO;KACb,MAAM;KACN,WAAW;KAIX,KAAK,0BAA6B,CAAC,UAAU;MAC3C,MAAM,aAAa,WAAW,QAAQ,UAAU;AAGhD,aAAO,MAAM,YACX,WACA,kBACK,gBAAgB,WAAW,MAC3B,SAAS,WAAW,GAC1B;KACF;IACF,EAAC;GACH;EACF;CACF;AACF"}
@@ -0,0 +1,75 @@
1
+ const require_ast = require('../core/ast.cjs');
2
+
3
+ //#region src/rules/require-meta-sink.ts
4
+ /**
5
+ * ESLint rule that detects `configure()` / `configureSync()` calls imported
6
+ * from `@logtape/logtape` that lack a dedicated sink for the meta logger.
7
+ *
8
+ * ```ts
9
+ * // Incorrect (missing meta sink):
10
+ * await configure({ sinks: { main: s }, loggers: [{ category: [], sinks: ["main"] }] });
11
+ *
12
+ * // Correct:
13
+ * await configure({
14
+ * sinks: { console: getConsoleSink(), main: s },
15
+ * loggers: [
16
+ * { category: ["logtape", "meta"], sinks: ["console"] },
17
+ * { category: [], sinks: ["main"] },
18
+ * ],
19
+ * });
20
+ * ```
21
+ */
22
+ const requireMetaSink = {
23
+ meta: {
24
+ type: "suggestion",
25
+ docs: {
26
+ description: "Require a dedicated sink for the LogTape meta logger in configure() calls",
27
+ recommended: true,
28
+ url: "https://logtape.org/lint/require-meta-sink"
29
+ },
30
+ schema: [],
31
+ messages: { requireMeta: "Add a dedicated sink for the meta logger (category: [\"logtape\"] or [\"logtape\", \"meta\"]) to handle LogTape's own diagnostic messages." }
32
+ },
33
+ create(context) {
34
+ const configFns = /* @__PURE__ */ new Set();
35
+ return {
36
+ ImportDeclaration(node) {
37
+ if (!require_ast.isLogtapeImportSource(node.source?.value)) return;
38
+ for (const specifier of node.specifiers ?? []) {
39
+ if (specifier.type !== "ImportSpecifier") continue;
40
+ const imported = specifier.imported?.name;
41
+ if (imported === "configure" || imported === "configureSync") configFns.add(specifier.local?.name);
42
+ }
43
+ },
44
+ CallExpression(node) {
45
+ if (configFns.size === 0) return;
46
+ const { callee } = node;
47
+ if (callee.type !== "Identifier") return;
48
+ const calleeName = callee.name;
49
+ if (!configFns.has(calleeName)) return;
50
+ const scope = context.sourceCode?.getScope?.(node) ?? context.getScope?.();
51
+ if (scope) {
52
+ let cur = scope;
53
+ let calleeVar = null;
54
+ while (cur) {
55
+ const v = cur.set?.get(calleeName);
56
+ if (v) {
57
+ calleeVar = v;
58
+ break;
59
+ }
60
+ cur = cur.upper;
61
+ }
62
+ if (!calleeVar?.defs?.some((d) => d.type === "ImportBinding")) return;
63
+ }
64
+ const configArg = require_ast.unwrapTypeAssertion(node.arguments[0]);
65
+ if (require_ast.configNeedsMetaSink(configArg)) context.report({
66
+ node,
67
+ messageId: "requireMeta"
68
+ });
69
+ }
70
+ };
71
+ }
72
+ };
73
+
74
+ //#endregion
75
+ exports.requireMetaSink = requireMetaSink;
@@ -0,0 +1,27 @@
1
+ import { Rule } from "eslint";
2
+
3
+ //#region src/rules/require-meta-sink.d.ts
4
+
5
+ /**
6
+ * ESLint rule that detects `configure()` / `configureSync()` calls imported
7
+ * from `@logtape/logtape` that lack a dedicated sink for the meta logger.
8
+ *
9
+ * ```ts
10
+ * // Incorrect (missing meta sink):
11
+ * await configure({ sinks: { main: s }, loggers: [{ category: [], sinks: ["main"] }] });
12
+ *
13
+ * // Correct:
14
+ * await configure({
15
+ * sinks: { console: getConsoleSink(), main: s },
16
+ * loggers: [
17
+ * { category: ["logtape", "meta"], sinks: ["console"] },
18
+ * { category: [], sinks: ["main"] },
19
+ * ],
20
+ * });
21
+ * ```
22
+ */
23
+ declare const requireMetaSink: Rule.RuleModule;
24
+ //# sourceMappingURL=require-meta-sink.d.ts.map
25
+ //#endregion
26
+ export { requireMetaSink };
27
+ //# sourceMappingURL=require-meta-sink.d.cts.map