@techsquidtv/eslint-plugin-structured-logging 0.1.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 (39) hide show
  1. package/LICENSE.md +20 -0
  2. package/README.md +167 -0
  3. package/docs/rules/require-logger-inline-attributes.md +22 -0
  4. package/docs/rules/require-logger-message.md +22 -0
  5. package/docs/rules/require-logger-primitive-attributes.md +23 -0
  6. package/docs/rules/require-logger-scoped-dot-notation.md +31 -0
  7. package/lib/index.d.ts +31 -0
  8. package/lib/index.d.ts.map +1 -0
  9. package/lib/index.js +55 -0
  10. package/lib/index.js.map +1 -0
  11. package/lib/package.js +6 -0
  12. package/lib/package.js.map +1 -0
  13. package/lib/rules/index.d.ts +5 -0
  14. package/lib/rules/index.js +5 -0
  15. package/lib/rules/logger/require-logger-inline-attributes.d.ts +13 -0
  16. package/lib/rules/logger/require-logger-inline-attributes.d.ts.map +1 -0
  17. package/lib/rules/logger/require-logger-inline-attributes.js +46 -0
  18. package/lib/rules/logger/require-logger-inline-attributes.js.map +1 -0
  19. package/lib/rules/logger/require-logger-message.d.ts +13 -0
  20. package/lib/rules/logger/require-logger-message.d.ts.map +1 -0
  21. package/lib/rules/logger/require-logger-message.js +46 -0
  22. package/lib/rules/logger/require-logger-message.js.map +1 -0
  23. package/lib/rules/logger/require-logger-primitive-attributes.d.ts +20 -0
  24. package/lib/rules/logger/require-logger-primitive-attributes.d.ts.map +1 -0
  25. package/lib/rules/logger/require-logger-primitive-attributes.js +48 -0
  26. package/lib/rules/logger/require-logger-primitive-attributes.js.map +1 -0
  27. package/lib/rules/logger/require-logger-scoped-dot-notation.d.ts +21 -0
  28. package/lib/rules/logger/require-logger-scoped-dot-notation.d.ts.map +1 -0
  29. package/lib/rules/logger/require-logger-scoped-dot-notation.js +111 -0
  30. package/lib/rules/logger/require-logger-scoped-dot-notation.js.map +1 -0
  31. package/lib/rules/logger/utils.d.ts +156 -0
  32. package/lib/rules/logger/utils.d.ts.map +1 -0
  33. package/lib/rules/logger/utils.js +271 -0
  34. package/lib/rules/logger/utils.js.map +1 -0
  35. package/lib/utils.d.ts +17 -0
  36. package/lib/utils.d.ts.map +1 -0
  37. package/lib/utils.js +7 -0
  38. package/lib/utils.js.map +1 -0
  39. package/package.json +80 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-logger-primitive-attributes.js","names":[],"sources":["../../../src/rules/logger/require-logger-primitive-attributes.ts"],"sourcesContent":["import { createRule } from \"@/utils\";\nimport {\n\tDEFAULT_LOGGER_MATCHER_OPTIONS,\n\tLOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES,\n\tbuildLoggerMatcherSets,\n\tcollectAttrsEntries,\n\tgetLoggerArguments,\n\tisDisallowedAttributeValue,\n\ttype LoggerMatcherOptions,\n} from \"@/rules/logger/utils\";\n\ntype MessageIds = \"primitiveAttributeValue\";\n\n/** Options for primitive logger attribute enforcement. */\nexport interface RequireLoggerPrimitiveAttributesOptions extends LoggerMatcherOptions {\n\t/**\n\t * Reject expressions whose static value shape is unknown instead of\n\t * allowing them as attribute values.\n\t */\n\tdisallowUnknownAttributeValues?: boolean;\n}\n\ntype RuleOptions = [RequireLoggerPrimitiveAttributesOptions];\n\n/** Enforces primitive values (or arrays of primitives) for logger attributes. */\nexport const requireLoggerPrimitiveAttributes = createRule<\n\tRuleOptions,\n\tMessageIds\n>({\n\tname: \"require-logger-primitive-attributes\",\n\tmeta: {\n\t\ttype: \"problem\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Require logger attribute values to be primitives or arrays of primitives.\",\n\t\t\trecommended: true,\n\t\t},\n\t\tmessages: {\n\t\t\tprimitiveAttributeValue:\n\t\t\t\t'Attribute \"{{key}}\" must be a primitive or an array of primitives. Use a string, number, or boolean (or an array of those), or flatten nested data into separate attributes.',\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\t...LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES,\n\t\t\t\t\tdisallowUnknownAttributeValues: {\n\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\"Reject expressions whose static value shape is unknown instead of allowing them as attribute values.\",\n\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t\tdefaultOptions: [\n\t\t\t{\n\t\t\t\t...DEFAULT_LOGGER_MATCHER_OPTIONS,\n\t\t\t\tdisallowUnknownAttributeValues: false,\n\t\t\t},\n\t\t],\n\t},\n\tcreate(context) {\n\t\tconst sets = buildLoggerMatcherSets(context.options[0]);\n\n\t\treturn {\n\t\t\tCallExpression(node) {\n\t\t\t\tconst args = getLoggerArguments(node, sets);\n\t\t\t\tif (!args) return;\n\n\t\t\t\tconst entries = collectAttrsEntries(args.attrsArg);\n\t\t\t\tif (!entries) return;\n\n\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\tif (isDisallowedAttributeValue(entry.valueNode, context.options[0])) {\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode: entry.valueNode,\n\t\t\t\t\t\t\tmessageId: \"primitiveAttributeValue\",\n\t\t\t\t\t\t\tdata: { key: entry.key },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t},\n});\n"],"mappings":";;;;AAyBA,MAAa,mCAAmC,WAG9C;CACD,MAAM;CACN,MAAM;EACL,MAAM;EACN,MAAM;GACL,aACC;GACD,aAAa;EACd;EACA,UAAU,EACT,yBACC,iLACF;EACA,QAAQ,CACP;GACC,MAAM;GACN,YAAY;IACX,GAAG;IACH,gCAAgC;KAC/B,aACC;KACD,MAAM;IACP;GACD;GACA,sBAAsB;EACvB,CACD;EACA,gBAAgB,CACf;GACC,GAAG;GACH,gCAAgC;EACjC,CACD;CACD;CACA,OAAO,SAAS;EACf,MAAM,OAAO,uBAAuB,QAAQ,QAAQ,EAAE;EAEtD,OAAO,EACN,eAAe,MAAM;GACpB,MAAM,OAAO,mBAAmB,MAAM,IAAI;GAC1C,IAAI,CAAC,MAAM;GAEX,MAAM,UAAU,oBAAoB,KAAK,QAAQ;GACjD,IAAI,CAAC,SAAS;GAEd,KAAK,MAAM,SAAS,SACnB,IAAI,2BAA2B,MAAM,WAAW,QAAQ,QAAQ,EAAE,GACjE,QAAQ,OAAO;IACd,MAAM,MAAM;IACZ,WAAW;IACX,MAAM,EAAE,KAAK,MAAM,IAAI;GACxB,CAAC;EAGJ,EACD;CACD;AACD,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { LoggerMatcherOptions } from "./utils.js";
2
+ import { StructuredLoggingPluginDocs } from "../../utils.js";
3
+
4
+ //#region src/rules/logger/require-logger-scoped-dot-notation.d.ts
5
+ type MessageIds = "dottedSnakeCaseAttributeKey" | "dottedSnakeCaseMessage";
6
+ type DottedSnakeCaseFormat = "dotted-snake-case" | "off";
7
+ /** Options for dotted snake case logger message and attribute enforcement. */
8
+ interface RequireLoggerScopedDotNotationOptions extends LoggerMatcherOptions {
9
+ /** Format required for inline attribute keys. */
10
+ attributeKeyFormat?: DottedSnakeCaseFormat;
11
+ /** Format required for logger messages. */
12
+ messageFormat?: DottedSnakeCaseFormat;
13
+ }
14
+ type RuleOptions = [RequireLoggerScopedDotNotationOptions];
15
+ /** Enforces dotted snake case for logger messages and attribute keys. */
16
+ declare const requireLoggerScopedDotNotation: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, RuleOptions, StructuredLoggingPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
17
+ name: string;
18
+ };
19
+ //#endregion
20
+ export { DottedSnakeCaseFormat, RequireLoggerScopedDotNotationOptions, requireLoggerScopedDotNotation };
21
+ //# sourceMappingURL=require-logger-scoped-dot-notation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-logger-scoped-dot-notation.d.ts","names":[],"sources":["../../../src/rules/logger/require-logger-scoped-dot-notation.ts"],"mappings":";;;;KAaK,UAAA;AAAA,KACO,qBAAA;;UAGK,qCAAA,SAA8C,oBAAA;EAJhD;EAMd,kBAAA,GAAqB,qBAAA;EANP;EAQd,aAAA,GAAgB,qBAAA;AAAA;AAAA,KAGZ,WAAA,IAAe,qCAAqC;;cAoD5C,8BAAA,+CAA8B,UAAA,CAAA,UAAA,EAAA,WAAA,EAAA,2BAAA,+CAAA,YAAA"}
@@ -0,0 +1,111 @@
1
+ import { createRule } from "../../utils.js";
2
+ import { DEFAULT_LOGGER_MATCHER_OPTIONS, DOTTED_SNAKE_CASE_SEGMENT_RE, LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES, buildLoggerMatcherSets, collectAttrsEntries, getLoggerArguments } from "./utils.js";
3
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
+ //#region src/rules/logger/require-logger-scoped-dot-notation.ts
5
+ const DOTTED_SNAKE_CASE_FORMAT = "dotted-snake-case";
6
+ /** Converts one dotted snake case segment to lower snake_case. */
7
+ function toDottedSnakeCaseSegment(segment) {
8
+ return segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
9
+ }
10
+ /** Converts every existing segment of a dotted name to lower snake_case. */
11
+ function toDottedSnakeCaseName(name) {
12
+ return name.split(".").map(toDottedSnakeCaseSegment).join(".");
13
+ }
14
+ /** Returns a safe dotted snake case autofix, or null when no safe fix exists. */
15
+ function getDottedSnakeCaseFix(name) {
16
+ const fixedName = toDottedSnakeCaseName(name);
17
+ return fixedName.includes(".") && isDottedSnakeCaseName(fixedName) ? fixedName : null;
18
+ }
19
+ /** Returns whether a static logger name is dotted lower snake_case. */
20
+ function isDottedSnakeCaseName(name) {
21
+ const segments = name.split(".");
22
+ return segments.length >= 2 && segments.every((segment) => segment.length > 0 && DOTTED_SNAKE_CASE_SEGMENT_RE.test(segment));
23
+ }
24
+ /**
25
+ * Quotes an autofixed string literal using the original quote style and escapes
26
+ * backslashes plus matching quote characters.
27
+ */
28
+ function quoteStringLike(raw, value) {
29
+ const quote = raw.startsWith("'") ? "'" : "\"";
30
+ return `${quote}${value.replace(/\\/g, "\\\\").replace(new RegExp(quote, "g"), `\\${quote}`)}${quote}`;
31
+ }
32
+ /** Enforces dotted snake case for logger messages and attribute keys. */
33
+ const requireLoggerScopedDotNotation = createRule({
34
+ name: "require-logger-scoped-dot-notation",
35
+ meta: {
36
+ type: "problem",
37
+ fixable: "code",
38
+ docs: {
39
+ description: "Require logger messages and attribute keys to use dotted snake case.",
40
+ recommended: true
41
+ },
42
+ messages: {
43
+ dottedSnakeCaseAttributeKey: "Attribute key must use dotted snake case: \"{{name}}\". Use at least two lowercase dotted segments, for example \"payment.id\".",
44
+ dottedSnakeCaseMessage: "Logger message must use dotted snake case: \"{{name}}\". Use at least two lowercase dotted segments, for example \"payment.capture.failed\"."
45
+ },
46
+ schema: [{
47
+ type: "object",
48
+ properties: {
49
+ ...LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES,
50
+ attributeKeyFormat: {
51
+ description: "Format required for inline attribute keys.",
52
+ enum: [DOTTED_SNAKE_CASE_FORMAT, "off"],
53
+ type: "string"
54
+ },
55
+ messageFormat: {
56
+ description: "Format required for logger messages.",
57
+ enum: [DOTTED_SNAKE_CASE_FORMAT, "off"],
58
+ type: "string"
59
+ }
60
+ },
61
+ additionalProperties: false
62
+ }],
63
+ defaultOptions: [{
64
+ ...DEFAULT_LOGGER_MATCHER_OPTIONS,
65
+ attributeKeyFormat: DOTTED_SNAKE_CASE_FORMAT,
66
+ messageFormat: DOTTED_SNAKE_CASE_FORMAT
67
+ }]
68
+ },
69
+ create(context) {
70
+ const options = context.options[0];
71
+ const sets = buildLoggerMatcherSets(options);
72
+ const sourceCode = context.sourceCode;
73
+ return { CallExpression(node) {
74
+ const args = getLoggerArguments(node, sets);
75
+ if (!args) return;
76
+ const messageArg = args.messageArg;
77
+ if (options.messageFormat === DOTTED_SNAKE_CASE_FORMAT && messageArg?.type === AST_NODE_TYPES.Literal && typeof messageArg.value === "string" && !isDottedSnakeCaseName(messageArg.value)) {
78
+ const fixedMessage = getDottedSnakeCaseFix(messageArg.value);
79
+ context.report({
80
+ node: messageArg,
81
+ messageId: "dottedSnakeCaseMessage",
82
+ data: { name: messageArg.value },
83
+ fix: fixedMessage ? (fixer) => fixer.replaceText(messageArg, quoteStringLike(sourceCode.getText(messageArg), fixedMessage)) : null
84
+ });
85
+ }
86
+ if (options.attributeKeyFormat === DOTTED_SNAKE_CASE_FORMAT) {
87
+ const entries = collectAttrsEntries(args.attrsArg);
88
+ if (!entries) return;
89
+ for (const entry of entries) {
90
+ if (isDottedSnakeCaseName(entry.key)) continue;
91
+ const fixedKey = getDottedSnakeCaseFix(entry.key);
92
+ context.report({
93
+ node: entry.keyNode,
94
+ messageId: "dottedSnakeCaseAttributeKey",
95
+ data: { name: entry.key },
96
+ fix: fixedKey ? (fixer) => {
97
+ const keyText = sourceCode.getText(entry.keyNode);
98
+ if (entry.keyNode.type === AST_NODE_TYPES.Identifier && entry.valueNode.type === AST_NODE_TYPES.Identifier && entry.keyNode.range[0] === entry.valueNode.range[0] && entry.keyNode.range[1] === entry.valueNode.range[1]) return fixer.replaceText(entry.keyNode, `${fixedKey}: ${keyText}`);
99
+ if (entry.keyNode.type === AST_NODE_TYPES.Literal) return fixer.replaceText(entry.keyNode, quoteStringLike(keyText, fixedKey));
100
+ return fixer.replaceText(entry.keyNode, fixedKey);
101
+ } : null
102
+ });
103
+ }
104
+ }
105
+ } };
106
+ }
107
+ });
108
+ //#endregion
109
+ export { requireLoggerScopedDotNotation };
110
+
111
+ //# sourceMappingURL=require-logger-scoped-dot-notation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-logger-scoped-dot-notation.js","names":[],"sources":["../../../src/rules/logger/require-logger-scoped-dot-notation.ts"],"sourcesContent":["import { AST_NODE_TYPES } from \"@typescript-eslint/utils\";\n\nimport { createRule } from \"@/utils\";\nimport {\n\tLOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES,\n\tDOTTED_SNAKE_CASE_SEGMENT_RE,\n\tDEFAULT_LOGGER_MATCHER_OPTIONS,\n\tbuildLoggerMatcherSets,\n\tcollectAttrsEntries,\n\tgetLoggerArguments,\n\ttype LoggerMatcherOptions,\n} from \"@/rules/logger/utils\";\n\ntype MessageIds = \"dottedSnakeCaseAttributeKey\" | \"dottedSnakeCaseMessage\";\nexport type DottedSnakeCaseFormat = \"dotted-snake-case\" | \"off\";\n\n/** Options for dotted snake case logger message and attribute enforcement. */\nexport interface RequireLoggerScopedDotNotationOptions extends LoggerMatcherOptions {\n\t/** Format required for inline attribute keys. */\n\tattributeKeyFormat?: DottedSnakeCaseFormat;\n\t/** Format required for logger messages. */\n\tmessageFormat?: DottedSnakeCaseFormat;\n}\n\ntype RuleOptions = [RequireLoggerScopedDotNotationOptions];\n\nconst DOTTED_SNAKE_CASE_FORMAT: DottedSnakeCaseFormat = \"dotted-snake-case\";\n\n/** Converts one dotted snake case segment to lower snake_case. */\nfunction toDottedSnakeCaseSegment(segment: string): string {\n\treturn segment\n\t\t.replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n\t\t.replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\")\n\t\t.replace(/[-\\s]+/g, \"_\")\n\t\t.toLowerCase();\n}\n\n/** Converts every existing segment of a dotted name to lower snake_case. */\nfunction toDottedSnakeCaseName(name: string): string {\n\treturn name.split(\".\").map(toDottedSnakeCaseSegment).join(\".\");\n}\n\n/** Returns a safe dotted snake case autofix, or null when no safe fix exists. */\nfunction getDottedSnakeCaseFix(name: string): string | null {\n\tconst fixedName = toDottedSnakeCaseName(name);\n\treturn fixedName.includes(\".\") && isDottedSnakeCaseName(fixedName)\n\t\t? fixedName\n\t\t: null;\n}\n\n/** Returns whether a static logger name is dotted lower snake_case. */\nfunction isDottedSnakeCaseName(name: string): boolean {\n\tconst segments = name.split(\".\");\n\treturn (\n\t\tsegments.length >= 2 &&\n\t\tsegments.every(\n\t\t\t(segment) =>\n\t\t\t\tsegment.length > 0 && DOTTED_SNAKE_CASE_SEGMENT_RE.test(segment),\n\t\t)\n\t);\n}\n\n/**\n * Quotes an autofixed string literal using the original quote style and escapes\n * backslashes plus matching quote characters.\n */\nfunction quoteStringLike(raw: string, value: string): string {\n\tconst quote = raw.startsWith(\"'\") ? \"'\" : '\"';\n\tconst escaped = value\n\t\t.replace(/\\\\/g, \"\\\\\\\\\")\n\t\t.replace(new RegExp(quote, \"g\"), `\\\\${quote}`);\n\n\treturn `${quote}${escaped}${quote}`;\n}\n\n/** Enforces dotted snake case for logger messages and attribute keys. */\nexport const requireLoggerScopedDotNotation = createRule<\n\tRuleOptions,\n\tMessageIds\n>({\n\tname: \"require-logger-scoped-dot-notation\",\n\tmeta: {\n\t\ttype: \"problem\",\n\t\tfixable: \"code\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Require logger messages and attribute keys to use dotted snake case.\",\n\t\t\trecommended: true,\n\t\t},\n\t\tmessages: {\n\t\t\tdottedSnakeCaseAttributeKey:\n\t\t\t\t'Attribute key must use dotted snake case: \"{{name}}\". Use at least two lowercase dotted segments, for example \"payment.id\".',\n\t\t\tdottedSnakeCaseMessage:\n\t\t\t\t'Logger message must use dotted snake case: \"{{name}}\". Use at least two lowercase dotted segments, for example \"payment.capture.failed\".',\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\t...LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES,\n\t\t\t\t\tattributeKeyFormat: {\n\t\t\t\t\t\tdescription: \"Format required for inline attribute keys.\",\n\t\t\t\t\t\tenum: [DOTTED_SNAKE_CASE_FORMAT, \"off\"],\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t},\n\t\t\t\t\tmessageFormat: {\n\t\t\t\t\t\tdescription: \"Format required for logger messages.\",\n\t\t\t\t\t\tenum: [DOTTED_SNAKE_CASE_FORMAT, \"off\"],\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t\tdefaultOptions: [\n\t\t\t{\n\t\t\t\t...DEFAULT_LOGGER_MATCHER_OPTIONS,\n\t\t\t\tattributeKeyFormat: DOTTED_SNAKE_CASE_FORMAT,\n\t\t\t\tmessageFormat: DOTTED_SNAKE_CASE_FORMAT,\n\t\t\t},\n\t\t],\n\t},\n\tcreate(context) {\n\t\tconst options = context.options[0];\n\t\tconst sets = buildLoggerMatcherSets(options);\n\t\tconst sourceCode = context.sourceCode;\n\n\t\treturn {\n\t\t\tCallExpression(node) {\n\t\t\t\tconst args = getLoggerArguments(node, sets);\n\t\t\t\tif (!args) return;\n\n\t\t\t\tconst messageArg = args.messageArg;\n\t\t\t\tif (\n\t\t\t\t\toptions.messageFormat === DOTTED_SNAKE_CASE_FORMAT &&\n\t\t\t\t\tmessageArg?.type === AST_NODE_TYPES.Literal &&\n\t\t\t\t\ttypeof messageArg.value === \"string\" &&\n\t\t\t\t\t!isDottedSnakeCaseName(messageArg.value)\n\t\t\t\t) {\n\t\t\t\t\tconst fixedMessage = getDottedSnakeCaseFix(messageArg.value);\n\t\t\t\t\tcontext.report({\n\t\t\t\t\t\tnode: messageArg,\n\t\t\t\t\t\tmessageId: \"dottedSnakeCaseMessage\",\n\t\t\t\t\t\tdata: { name: messageArg.value },\n\t\t\t\t\t\tfix: fixedMessage\n\t\t\t\t\t\t\t? (fixer) =>\n\t\t\t\t\t\t\t\t\tfixer.replaceText(\n\t\t\t\t\t\t\t\t\t\tmessageArg,\n\t\t\t\t\t\t\t\t\t\tquoteStringLike(\n\t\t\t\t\t\t\t\t\t\t\tsourceCode.getText(messageArg),\n\t\t\t\t\t\t\t\t\t\t\tfixedMessage,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (options.attributeKeyFormat === DOTTED_SNAKE_CASE_FORMAT) {\n\t\t\t\t\tconst entries = collectAttrsEntries(args.attrsArg);\n\t\t\t\t\tif (!entries) return;\n\n\t\t\t\t\tfor (const entry of entries) {\n\t\t\t\t\t\tif (isDottedSnakeCaseName(entry.key)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst fixedKey = getDottedSnakeCaseFix(entry.key);\n\t\t\t\t\t\tcontext.report({\n\t\t\t\t\t\t\tnode: entry.keyNode,\n\t\t\t\t\t\t\tmessageId: \"dottedSnakeCaseAttributeKey\",\n\t\t\t\t\t\t\tdata: { name: entry.key },\n\t\t\t\t\t\t\tfix: fixedKey\n\t\t\t\t\t\t\t\t? (fixer) => {\n\t\t\t\t\t\t\t\t\t\tconst keyText = sourceCode.getText(entry.keyNode);\n\n\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\tentry.keyNode.type === AST_NODE_TYPES.Identifier &&\n\t\t\t\t\t\t\t\t\t\t\tentry.valueNode.type === AST_NODE_TYPES.Identifier &&\n\t\t\t\t\t\t\t\t\t\t\tentry.keyNode.range[0] === entry.valueNode.range[0] &&\n\t\t\t\t\t\t\t\t\t\t\tentry.keyNode.range[1] === entry.valueNode.range[1]\n\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\treturn fixer.replaceText(\n\t\t\t\t\t\t\t\t\t\t\t\tentry.keyNode,\n\t\t\t\t\t\t\t\t\t\t\t\t`${fixedKey}: ${keyText}`,\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tif (entry.keyNode.type === AST_NODE_TYPES.Literal) {\n\t\t\t\t\t\t\t\t\t\t\treturn fixer.replaceText(\n\t\t\t\t\t\t\t\t\t\t\t\tentry.keyNode,\n\t\t\t\t\t\t\t\t\t\t\t\tquoteStringLike(keyText, fixedKey),\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\treturn fixer.replaceText(entry.keyNode, fixedKey);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t},\n});\n"],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ import { TSESTree } from "@typescript-eslint/utils";
2
+
3
+ //#region src/rules/logger/utils.d.ts
4
+ /** Matches a single lowercase snake_case dotted-name segment. */
5
+ declare const DOTTED_SNAKE_CASE_SEGMENT_RE: RegExp;
6
+ /** Shared JSON schema properties for rules that match logger calls. */
7
+ declare const LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES: {
8
+ readonly allowedLoggerObjects: {
9
+ readonly description: "Fully-qualified logger object paths, such as `log.child`.";
10
+ readonly type: "array";
11
+ readonly items: {
12
+ readonly type: "string";
13
+ };
14
+ };
15
+ readonly allowedLoggerIdentifiers: {
16
+ readonly description: "Bare logger object identifiers accepted as logger callees.";
17
+ readonly type: "array";
18
+ readonly items: {
19
+ readonly type: "string";
20
+ };
21
+ };
22
+ readonly attributesFirstLoggerObjects: {
23
+ readonly description: "Logger object paths whose calls pass attributes before the message.";
24
+ readonly type: "array";
25
+ readonly items: {
26
+ readonly type: "string";
27
+ };
28
+ };
29
+ readonly attributesFirstLoggerIdentifiers: {
30
+ readonly description: "Logger identifiers whose calls pass attributes before the message.";
31
+ readonly type: "array";
32
+ readonly items: {
33
+ readonly type: "string";
34
+ };
35
+ };
36
+ readonly levelMethods: {
37
+ readonly description: "Method names treated as logger level calls, such as `info` or `error`.";
38
+ readonly type: "array";
39
+ readonly items: {
40
+ readonly type: "string";
41
+ };
42
+ };
43
+ readonly ignoreDynamicLevelMethods: {
44
+ readonly description: "Skip computed logger level methods such as `logger[level](...)` instead of matching them.";
45
+ readonly type: "boolean";
46
+ };
47
+ };
48
+ interface LoggerMatcherOptions {
49
+ /** Fully-qualified logger object paths, such as `log.child`. */
50
+ allowedLoggerObjects?: string[];
51
+ /** Bare logger object identifiers accepted as logger callees. */
52
+ allowedLoggerIdentifiers?: string[];
53
+ /** Logger object paths whose calls pass attributes before the message. */
54
+ attributesFirstLoggerObjects?: string[];
55
+ /** Logger identifiers whose calls pass attributes before the message. */
56
+ attributesFirstLoggerIdentifiers?: string[];
57
+ /** Method names treated as logger level calls, such as `info` or `error`. */
58
+ levelMethods?: string[];
59
+ /** Skip computed logger level methods such as `logger[level](...)` instead of matching them. */
60
+ ignoreDynamicLevelMethods?: boolean;
61
+ }
62
+ /**
63
+ * Pre-built sets derived from {@link LoggerMatcherOptions}.
64
+ * Construct once per file via {@link buildLoggerMatcherSets} in `create()` to
65
+ * avoid re-allocating on every `CallExpression` node visit.
66
+ */
67
+ interface LoggerMatcherSets {
68
+ /** Pre-built set for fully-qualified logger object paths. */
69
+ allowedLoggerObjects: Set<string>;
70
+ /** Pre-built set for bare logger object identifiers. */
71
+ allowedLoggerIdentifiers: Set<string>;
72
+ /** Pre-built set for object paths using attributes-first argument order. */
73
+ attributesFirstLoggerObjects: Set<string>;
74
+ /** Pre-built set for identifiers using attributes-first argument order. */
75
+ attributesFirstLoggerIdentifiers: Set<string>;
76
+ /** Pre-built set of method names treated as logger level calls. */
77
+ levelMethods: Set<string>;
78
+ /** Whether to skip computed logger level methods instead of matching them. */
79
+ ignoreDynamicLevelMethods: boolean;
80
+ }
81
+ /** A statically-known key/value pair from an inline attributes object. */
82
+ interface AttrEntry {
83
+ /** Attribute key exactly as written, with literal keys normalized to strings. */
84
+ key: string;
85
+ /** AST node for the attribute key, used as the report and fix target. */
86
+ keyNode: TSESTree.Node;
87
+ /** AST expression for the attribute value. */
88
+ valueNode: TSESTree.Expression;
89
+ }
90
+ /** Controls which malformed attribute shapes are reported while collecting. */
91
+ interface CollectAttrsOptions {
92
+ /** Report spreads and computed keys found inside inline attributes. */
93
+ reportMalformedProperties?: boolean;
94
+ /** Report non-object attributes arguments that cannot be statically checked. */
95
+ reportNonLiteral?: boolean;
96
+ /** Called when a reportable violation is found inside the attribute object. */
97
+ report?: (node: TSESTree.Node, messageId: string, data?: Record<string, string>) => void;
98
+ }
99
+ /** Default {@link LoggerMatcherOptions} shared across the logger rules. */
100
+ declare const DEFAULT_LOGGER_MATCHER_OPTIONS: Required<LoggerMatcherOptions>;
101
+ /**
102
+ * Convert rule options into pre-built `Set`s. Call this once in `create()` so
103
+ * sets are not rebuilt for every `CallExpression` in the linted file.
104
+ *
105
+ * Back-fills {@link DEFAULT_LOGGER_MATCHER_OPTIONS} for direct callers of
106
+ * {@link isLoggerCall} that pass partial or omitted options; rule callers
107
+ * already receive fully-resolved options from `meta.defaultOptions`.
108
+ */
109
+ declare function buildLoggerMatcherSets(options?: LoggerMatcherOptions): LoggerMatcherSets;
110
+ /**
111
+ * Convenience wrapper for external callers that don't pre-build sets.
112
+ * Within this plugin's rules, prefer {@link getLoggerArguments} with
113
+ * pre-built sets from {@link buildLoggerMatcherSets}.
114
+ */
115
+ declare function isLoggerCall(node: TSESTree.Node | null | undefined, options?: LoggerMatcherOptions): boolean;
116
+ /**
117
+ * Returns the message and attributes arguments for a logger call, or `null`
118
+ * if the node is not a recognised logger call. Use pre-built sets from
119
+ * {@link buildLoggerMatcherSets} so sets are not reallocated per-node.
120
+ */
121
+ declare function getLoggerArguments(node: TSESTree.CallExpression, sets: LoggerMatcherSets): {
122
+ attrsArg: TSESTree.Node | undefined;
123
+ messageArg: TSESTree.Node | undefined;
124
+ } | null;
125
+ /**
126
+ * Extracts key/value pairs from an attribute object literal.
127
+ * Returns `null` when the node cannot be analysed (not an ObjectExpression).
128
+ * Returns `[]` when there is no attributes argument at all.
129
+ */
130
+ declare function collectAttrsEntries(attrsNode: TSESTree.Node | null | undefined, options?: CollectAttrsOptions): AttrEntry[] | null;
131
+ /**
132
+ * Returns whether an attribute value should be rejected by primitive-value
133
+ * rules.
134
+ *
135
+ * @remarks
136
+ * Primitive values (string, number, boolean) are always allowed. Arrays are
137
+ * allowed when every element is itself a primitive value, so arrays of scalars
138
+ * such as `["a", "b"]` pass while nested arrays and arrays of objects do not.
139
+ * Objects, functions, and classes are always rejected. Unknown expressions
140
+ * (including array spreads) are rejected only when
141
+ * `disallowUnknownAttributeValues` is `true`.
142
+ */
143
+ declare function isDisallowedAttributeValue(node: TSESTree.Node | null | undefined, options?: {
144
+ disallowUnknownAttributeValues?: boolean;
145
+ }): boolean;
146
+ /**
147
+ * Returns whether a candidate logger message is clearly not static text.
148
+ *
149
+ * @remarks
150
+ * This intentionally returns `false` for a missing node so callers can report a
151
+ * dedicated "message required" violation.
152
+ */
153
+ declare function isClearlyNotMessage(node: TSESTree.Node | null | undefined): boolean;
154
+ //#endregion
155
+ export { AttrEntry, CollectAttrsOptions, DEFAULT_LOGGER_MATCHER_OPTIONS, DOTTED_SNAKE_CASE_SEGMENT_RE, LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES, LoggerMatcherOptions, LoggerMatcherSets, buildLoggerMatcherSets, collectAttrsEntries, getLoggerArguments, isClearlyNotMessage, isDisallowedAttributeValue, isLoggerCall };
156
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/rules/logger/utils.ts"],"mappings":";;;;cAKa,4BAAA,EAA4B,MAAsB;AAA/D;AAAA,cA6Ba,uCAAA;EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAsCI,oBAAA;EAEhB;EAAA,oBAAA;EAIA;EAFA,wBAAA;EAMA;EAJA,4BAAA;EAMyB;EAJzB,gCAAA;EAYgB;EAVhB,YAAA;;EAEA,yBAAA;AAAA;;;;;;UAQgB,iBAAA;EAEM;EAAtB,oBAAA,EAAsB,GAAA;EAEI;EAA1B,wBAAA,EAA0B,GAAA;EAEI;EAA9B,4BAAA,EAA8B,GAAA;EAEI;EAAlC,gCAAA,EAAkC,GAAA;EAEpB;EAAd,YAAA,EAAc,GAAA;EAEW;EAAzB,yBAAA;AAAA;;UAIgB,SAAA;EAMc;EAJ9B,GAAA;EAEA;EAAA,OAAA,EAAS,QAAA,CAAS,IAAA;EAAA;EAElB,SAAA,EAAW,QAAA,CAAS,UAAU;AAAA;;UAId,mBAAA;EAJc;EAM9B,yBAAA;EAFmC;EAInC,gBAAA;EAKc;EAHd,MAAA,IACC,IAAA,EAAM,QAAA,CAAS,IAAA,EACf,SAAA,UACA,IAAA,GAAO,MAAM;AAAA;;cAcF,8BAAA,EAAgC,QAAQ,CAAC,oBAAA;;;;;;;AAdvB;AAc/B;iBAiBgB,sBAAA,CACf,OAAA,GAAS,oBAAA,GACP,iBAAiB;;;AAnBsD;AAiB1E;;iBAoJgB,YAAA,CACf,IAAA,EAAM,QAAA,CAAS,IAAA,qBACf,OAAA,GAAS,oBAAyB;;;;;;iBAUnB,kBAAA,CACf,IAAA,EAAM,QAAA,CAAS,cAAA,EACf,IAAA,EAAM,iBAAA;EAEN,QAAA,EAAU,QAAA,CAAS,IAAA;EACnB,UAAA,EAAY,QAAA,CAAS,IAAA;AAAA;;;;;;iBAeN,mBAAA,CACf,SAAA,EAAW,QAAA,CAAS,IAAA,qBACpB,OAAA,GAAS,mBAAA,GACP,SAAA;;AAjCgC;AAUnC;;;;;;;;;;iBA6HgB,0BAAA,CACf,IAAA,EAAM,QAAA,CAAS,IAAI,qBACnB,OAAA;EAAW,8BAAA;AAAA;;;;;;;;iBAsBI,mBAAA,CACf,IAAA,EAAM,QAAA,CAAS,IAAI"}
@@ -0,0 +1,271 @@
1
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
2
+ //#region src/rules/logger/utils.ts
3
+ /** Matches a single lowercase snake_case dotted-name segment. */
4
+ const DOTTED_SNAKE_CASE_SEGMENT_RE = /^[a-z][a-z0-9_]*$/;
5
+ /**
6
+ * Default logger severity methods matched as level calls. Covers the level
7
+ * names shared by most structured loggers (pino, bunyan, and similar).
8
+ * Libraries with extra levels (for example winston's `http`/`verbose`/`silly`)
9
+ * can extend this set via the `levelMethods` rule option.
10
+ */
11
+ const DEFAULT_LEVEL_METHODS = [
12
+ "trace",
13
+ "debug",
14
+ "info",
15
+ "warn",
16
+ "error",
17
+ "fatal"
18
+ ];
19
+ /** Expression node shapes that are never valid primitive log attribute values. */
20
+ const NON_PRIMITIVE_NODE_TYPES = /* @__PURE__ */ new Set([
21
+ AST_NODE_TYPES.ObjectExpression,
22
+ AST_NODE_TYPES.ArrayExpression,
23
+ AST_NODE_TYPES.FunctionExpression,
24
+ AST_NODE_TYPES.ArrowFunctionExpression,
25
+ AST_NODE_TYPES.ClassExpression
26
+ ]);
27
+ /** Shared JSON schema properties for rules that match logger calls. */
28
+ const LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES = {
29
+ allowedLoggerObjects: {
30
+ description: "Fully-qualified logger object paths, such as `log.child`.",
31
+ type: "array",
32
+ items: { type: "string" }
33
+ },
34
+ allowedLoggerIdentifiers: {
35
+ description: "Bare logger object identifiers accepted as logger callees.",
36
+ type: "array",
37
+ items: { type: "string" }
38
+ },
39
+ attributesFirstLoggerObjects: {
40
+ description: "Logger object paths whose calls pass attributes before the message.",
41
+ type: "array",
42
+ items: { type: "string" }
43
+ },
44
+ attributesFirstLoggerIdentifiers: {
45
+ description: "Logger identifiers whose calls pass attributes before the message.",
46
+ type: "array",
47
+ items: { type: "string" }
48
+ },
49
+ levelMethods: {
50
+ description: "Method names treated as logger level calls, such as `info` or `error`.",
51
+ type: "array",
52
+ items: { type: "string" }
53
+ },
54
+ ignoreDynamicLevelMethods: {
55
+ description: "Skip computed logger level methods such as `logger[level](...)` instead of matching them.",
56
+ type: "boolean"
57
+ }
58
+ };
59
+ /** Default {@link LoggerMatcherOptions} shared across the logger rules. */
60
+ const DEFAULT_LOGGER_MATCHER_OPTIONS = {
61
+ allowedLoggerObjects: [],
62
+ allowedLoggerIdentifiers: ["logger", "log"],
63
+ attributesFirstLoggerObjects: [],
64
+ attributesFirstLoggerIdentifiers: [],
65
+ levelMethods: DEFAULT_LEVEL_METHODS,
66
+ ignoreDynamicLevelMethods: false
67
+ };
68
+ /**
69
+ * Convert rule options into pre-built `Set`s. Call this once in `create()` so
70
+ * sets are not rebuilt for every `CallExpression` in the linted file.
71
+ *
72
+ * Back-fills {@link DEFAULT_LOGGER_MATCHER_OPTIONS} for direct callers of
73
+ * {@link isLoggerCall} that pass partial or omitted options; rule callers
74
+ * already receive fully-resolved options from `meta.defaultOptions`.
75
+ */
76
+ function buildLoggerMatcherSets(options = {}) {
77
+ const resolved = {
78
+ ...DEFAULT_LOGGER_MATCHER_OPTIONS,
79
+ ...options
80
+ };
81
+ return {
82
+ allowedLoggerObjects: new Set(resolved.allowedLoggerObjects),
83
+ allowedLoggerIdentifiers: new Set(resolved.allowedLoggerIdentifiers),
84
+ attributesFirstLoggerObjects: new Set(resolved.attributesFirstLoggerObjects),
85
+ attributesFirstLoggerIdentifiers: new Set(resolved.attributesFirstLoggerIdentifiers),
86
+ levelMethods: new Set(resolved.levelMethods),
87
+ ignoreDynamicLevelMethods: resolved.ignoreDynamicLevelMethods
88
+ };
89
+ }
90
+ /**
91
+ * Removes optional-chaining wrapper nodes so logger-call analysis can inspect
92
+ * the underlying callee.
93
+ */
94
+ function unwrapChainExpression(node) {
95
+ if (node?.type === AST_NODE_TYPES.ChainExpression) return node.expression;
96
+ return node;
97
+ }
98
+ /**
99
+ * Reads a statically-known member property name from dot or string-literal
100
+ * bracket notation.
101
+ */
102
+ function getStaticPropertyName(node) {
103
+ if (node?.type !== AST_NODE_TYPES.MemberExpression) return null;
104
+ if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) return node.property.name;
105
+ if (node.computed && node.property.type === AST_NODE_TYPES.Literal && typeof node.property.value === "string") return node.property.value;
106
+ return null;
107
+ }
108
+ /**
109
+ * Converts a non-computed member expression chain into a dotted path such as
110
+ * `log.child`.
111
+ */
112
+ function getMemberPath(node) {
113
+ const segments = [];
114
+ let current = node;
115
+ while (current) {
116
+ if (current.type === AST_NODE_TYPES.Identifier) {
117
+ segments.unshift(current.name);
118
+ return segments.join(".");
119
+ }
120
+ if (current.type === AST_NODE_TYPES.MemberExpression && !current.computed) {
121
+ if (current.property.type !== AST_NODE_TYPES.Identifier) return null;
122
+ segments.unshift(current.property.name);
123
+ current = current.object;
124
+ continue;
125
+ }
126
+ return null;
127
+ }
128
+ return null;
129
+ }
130
+ /**
131
+ * Matches a call expression against the configured logger objects and returns
132
+ * its expected argument positions.
133
+ */
134
+ function getLoggerCallConfig(node, sets) {
135
+ if (node?.type !== AST_NODE_TYPES.CallExpression) return false;
136
+ const callee = unwrapChainExpression(node.callee);
137
+ if (callee?.type !== AST_NODE_TYPES.MemberExpression) return false;
138
+ const objectPath = getMemberPath(callee.object);
139
+ const objectIdentifier = callee.object.type === AST_NODE_TYPES.Identifier ? callee.object.name : null;
140
+ if (!(objectPath !== null && sets.allowedLoggerObjects.has(objectPath) || objectIdentifier !== null && sets.allowedLoggerIdentifiers.has(objectIdentifier))) return false;
141
+ const method = getStaticPropertyName(callee);
142
+ if (!(method ? sets.levelMethods.has(method) : callee.computed && !sets.ignoreDynamicLevelMethods)) return false;
143
+ const attributesFirst = objectPath !== null && sets.attributesFirstLoggerObjects.has(objectPath) || objectIdentifier !== null && sets.attributesFirstLoggerIdentifiers.has(objectIdentifier);
144
+ return {
145
+ attributesArgumentIndex: attributesFirst ? 0 : 1,
146
+ messageArgumentIndex: attributesFirst ? 1 : 0
147
+ };
148
+ }
149
+ /**
150
+ * Extracts a static object property key, rejecting computed keys that cannot be
151
+ * checked safely by logger attribute rules.
152
+ */
153
+ function getPropertyKey(prop) {
154
+ if (prop.computed) return null;
155
+ const key = prop.key;
156
+ if (key.type === AST_NODE_TYPES.Identifier) return key.name;
157
+ if (key.type === AST_NODE_TYPES.Literal) return String(key.value);
158
+ return null;
159
+ }
160
+ /**
161
+ * Convenience wrapper for external callers that don't pre-build sets.
162
+ * Within this plugin's rules, prefer {@link getLoggerArguments} with
163
+ * pre-built sets from {@link buildLoggerMatcherSets}.
164
+ */
165
+ function isLoggerCall(node, options = {}) {
166
+ return Boolean(getLoggerCallConfig(node, buildLoggerMatcherSets(options)));
167
+ }
168
+ /**
169
+ * Returns the message and attributes arguments for a logger call, or `null`
170
+ * if the node is not a recognised logger call. Use pre-built sets from
171
+ * {@link buildLoggerMatcherSets} so sets are not reallocated per-node.
172
+ */
173
+ function getLoggerArguments(node, sets) {
174
+ const config = getLoggerCallConfig(node, sets);
175
+ if (!config) return null;
176
+ return {
177
+ attrsArg: node.arguments[config.attributesArgumentIndex],
178
+ messageArg: node.arguments[config.messageArgumentIndex]
179
+ };
180
+ }
181
+ /**
182
+ * Extracts key/value pairs from an attribute object literal.
183
+ * Returns `null` when the node cannot be analysed (not an ObjectExpression).
184
+ * Returns `[]` when there is no attributes argument at all.
185
+ */
186
+ function collectAttrsEntries(attrsNode, options = {}) {
187
+ if (!attrsNode) return [];
188
+ const { reportMalformedProperties = false, reportNonLiteral = false, report } = options;
189
+ if (attrsNode.type !== AST_NODE_TYPES.ObjectExpression) {
190
+ if (reportNonLiteral) report?.(attrsNode, "nonInlineAttributes");
191
+ return null;
192
+ }
193
+ const entries = [];
194
+ for (const prop of attrsNode.properties) {
195
+ if (prop.type === AST_NODE_TYPES.SpreadElement) {
196
+ if (reportMalformedProperties) report?.(prop, "noAttributeSpread");
197
+ continue;
198
+ }
199
+ const key = getPropertyKey(prop);
200
+ if (!key) {
201
+ if (reportMalformedProperties) report?.(prop.key, "staticAttributeKeys");
202
+ continue;
203
+ }
204
+ entries.push({
205
+ key,
206
+ keyNode: prop.key,
207
+ valueNode: prop.value
208
+ });
209
+ }
210
+ return entries;
211
+ }
212
+ /** Returns whether a node is a literal string, number, or boolean value. */
213
+ function isScalarLiteral(node) {
214
+ return node?.type === AST_NODE_TYPES.Literal && (typeof node.value === "string" || typeof node.value === "number" || typeof node.value === "boolean");
215
+ }
216
+ /**
217
+ * Returns whether a node is a syntactic expression shape known to produce a
218
+ * scalar log attribute without requiring type information.
219
+ */
220
+ function isKnownScalarExpression(node) {
221
+ return isScalarLiteral(node) || node?.type === AST_NODE_TYPES.TemplateLiteral;
222
+ }
223
+ /**
224
+ * Returns whether a single (non-array) attribute value should be rejected as a
225
+ * primitive log value.
226
+ *
227
+ * @remarks
228
+ * Objects, arrays, functions, classes, and non-scalar literals are always
229
+ * rejected. Unknown expressions are rejected only when
230
+ * `disallowUnknownAttributeValues` is `true`.
231
+ */
232
+ function isDisallowedPrimitiveValue(node, options) {
233
+ if (node?.type !== void 0 && NON_PRIMITIVE_NODE_TYPES.has(node.type)) return true;
234
+ if (node?.type === AST_NODE_TYPES.Literal) return !isScalarLiteral(node);
235
+ return options.disallowUnknownAttributeValues === true && !isKnownScalarExpression(node);
236
+ }
237
+ /**
238
+ * Returns whether an attribute value should be rejected by primitive-value
239
+ * rules.
240
+ *
241
+ * @remarks
242
+ * Primitive values (string, number, boolean) are always allowed. Arrays are
243
+ * allowed when every element is itself a primitive value, so arrays of scalars
244
+ * such as `["a", "b"]` pass while nested arrays and arrays of objects do not.
245
+ * Objects, functions, and classes are always rejected. Unknown expressions
246
+ * (including array spreads) are rejected only when
247
+ * `disallowUnknownAttributeValues` is `true`.
248
+ */
249
+ function isDisallowedAttributeValue(node, options = {}) {
250
+ if (node?.type === AST_NODE_TYPES.ArrayExpression) return node.elements.some((element) => {
251
+ if (element === null) return false;
252
+ if (element.type === AST_NODE_TYPES.SpreadElement) return options.disallowUnknownAttributeValues === true;
253
+ return isDisallowedPrimitiveValue(element, options);
254
+ });
255
+ return isDisallowedPrimitiveValue(node, options);
256
+ }
257
+ /**
258
+ * Returns whether a candidate logger message is clearly not static text.
259
+ *
260
+ * @remarks
261
+ * This intentionally returns `false` for a missing node so callers can report a
262
+ * dedicated "message required" violation.
263
+ */
264
+ function isClearlyNotMessage(node) {
265
+ if (!node) return false;
266
+ return node.type !== AST_NODE_TYPES.Literal || typeof node.value !== "string";
267
+ }
268
+ //#endregion
269
+ export { DEFAULT_LOGGER_MATCHER_OPTIONS, DOTTED_SNAKE_CASE_SEGMENT_RE, LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES, buildLoggerMatcherSets, collectAttrsEntries, getLoggerArguments, isClearlyNotMessage, isDisallowedAttributeValue, isLoggerCall };
270
+
271
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/rules/logger/utils.ts"],"sourcesContent":["import { AST_NODE_TYPES, TSESTree } from \"@typescript-eslint/utils\";\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\n/** Matches a single lowercase snake_case dotted-name segment. */\nexport const DOTTED_SNAKE_CASE_SEGMENT_RE = /^[a-z][a-z0-9_]*$/;\n\n/**\n * Default logger severity methods matched as level calls. Covers the level\n * names shared by most structured loggers (pino, bunyan, and similar).\n * Libraries with extra levels (for example winston's `http`/`verbose`/`silly`)\n * can extend this set via the `levelMethods` rule option.\n */\nconst DEFAULT_LEVEL_METHODS = [\n\t\"trace\",\n\t\"debug\",\n\t\"info\",\n\t\"warn\",\n\t\"error\",\n\t\"fatal\",\n];\n\n/** Expression node shapes that are never valid primitive log attribute values. */\nconst NON_PRIMITIVE_NODE_TYPES = new Set<AST_NODE_TYPES>([\n\tAST_NODE_TYPES.ObjectExpression,\n\tAST_NODE_TYPES.ArrayExpression,\n\tAST_NODE_TYPES.FunctionExpression,\n\tAST_NODE_TYPES.ArrowFunctionExpression,\n\tAST_NODE_TYPES.ClassExpression,\n]);\n\n// ─── Shared option schema ─────────────────────────────────────────────────────\n\n/** Shared JSON schema properties for rules that match logger calls. */\nexport const LOGGER_MATCHER_OPTION_SCHEMA_PROPERTIES = {\n\tallowedLoggerObjects: {\n\t\tdescription: \"Fully-qualified logger object paths, such as `log.child`.\",\n\t\ttype: \"array\",\n\t\titems: { type: \"string\" },\n\t},\n\tallowedLoggerIdentifiers: {\n\t\tdescription: \"Bare logger object identifiers accepted as logger callees.\",\n\t\ttype: \"array\",\n\t\titems: { type: \"string\" },\n\t},\n\tattributesFirstLoggerObjects: {\n\t\tdescription:\n\t\t\t\"Logger object paths whose calls pass attributes before the message.\",\n\t\ttype: \"array\",\n\t\titems: { type: \"string\" },\n\t},\n\tattributesFirstLoggerIdentifiers: {\n\t\tdescription:\n\t\t\t\"Logger identifiers whose calls pass attributes before the message.\",\n\t\ttype: \"array\",\n\t\titems: { type: \"string\" },\n\t},\n\tlevelMethods: {\n\t\tdescription:\n\t\t\t\"Method names treated as logger level calls, such as `info` or `error`.\",\n\t\ttype: \"array\",\n\t\titems: { type: \"string\" },\n\t},\n\tignoreDynamicLevelMethods: {\n\t\tdescription:\n\t\t\t\"Skip computed logger level methods such as `logger[level](...)` instead of matching them.\",\n\t\ttype: \"boolean\",\n\t},\n} as const;\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface LoggerMatcherOptions {\n\t/** Fully-qualified logger object paths, such as `log.child`. */\n\tallowedLoggerObjects?: string[];\n\t/** Bare logger object identifiers accepted as logger callees. */\n\tallowedLoggerIdentifiers?: string[];\n\t/** Logger object paths whose calls pass attributes before the message. */\n\tattributesFirstLoggerObjects?: string[];\n\t/** Logger identifiers whose calls pass attributes before the message. */\n\tattributesFirstLoggerIdentifiers?: string[];\n\t/** Method names treated as logger level calls, such as `info` or `error`. */\n\tlevelMethods?: string[];\n\t/** Skip computed logger level methods such as `logger[level](...)` instead of matching them. */\n\tignoreDynamicLevelMethods?: boolean;\n}\n\n/**\n * Pre-built sets derived from {@link LoggerMatcherOptions}.\n * Construct once per file via {@link buildLoggerMatcherSets} in `create()` to\n * avoid re-allocating on every `CallExpression` node visit.\n */\nexport interface LoggerMatcherSets {\n\t/** Pre-built set for fully-qualified logger object paths. */\n\tallowedLoggerObjects: Set<string>;\n\t/** Pre-built set for bare logger object identifiers. */\n\tallowedLoggerIdentifiers: Set<string>;\n\t/** Pre-built set for object paths using attributes-first argument order. */\n\tattributesFirstLoggerObjects: Set<string>;\n\t/** Pre-built set for identifiers using attributes-first argument order. */\n\tattributesFirstLoggerIdentifiers: Set<string>;\n\t/** Pre-built set of method names treated as logger level calls. */\n\tlevelMethods: Set<string>;\n\t/** Whether to skip computed logger level methods instead of matching them. */\n\tignoreDynamicLevelMethods: boolean;\n}\n\n/** A statically-known key/value pair from an inline attributes object. */\nexport interface AttrEntry {\n\t/** Attribute key exactly as written, with literal keys normalized to strings. */\n\tkey: string;\n\t/** AST node for the attribute key, used as the report and fix target. */\n\tkeyNode: TSESTree.Node;\n\t/** AST expression for the attribute value. */\n\tvalueNode: TSESTree.Expression;\n}\n\n/** Controls which malformed attribute shapes are reported while collecting. */\nexport interface CollectAttrsOptions {\n\t/** Report spreads and computed keys found inside inline attributes. */\n\treportMalformedProperties?: boolean;\n\t/** Report non-object attributes arguments that cannot be statically checked. */\n\treportNonLiteral?: boolean;\n\t/** Called when a reportable violation is found inside the attribute object. */\n\treport?: (\n\t\tnode: TSESTree.Node,\n\t\tmessageId: string,\n\t\tdata?: Record<string, string>,\n\t) => void;\n}\n\ninterface LoggerCallConfig {\n\t/** Zero-based index of the attributes argument for this logger call. */\n\tattributesArgumentIndex: number;\n\t/** Zero-based index of the message argument for this logger call. */\n\tmessageArgumentIndex: number;\n}\n\n// ─── Logger matcher set helpers ───────────────────────────────────────────────\n\n/** Default {@link LoggerMatcherOptions} shared across the logger rules. */\nexport const DEFAULT_LOGGER_MATCHER_OPTIONS: Required<LoggerMatcherOptions> = {\n\tallowedLoggerObjects: [],\n\tallowedLoggerIdentifiers: [\"logger\", \"log\"],\n\tattributesFirstLoggerObjects: [],\n\tattributesFirstLoggerIdentifiers: [],\n\tlevelMethods: DEFAULT_LEVEL_METHODS,\n\tignoreDynamicLevelMethods: false,\n};\n\n/**\n * Convert rule options into pre-built `Set`s. Call this once in `create()` so\n * sets are not rebuilt for every `CallExpression` in the linted file.\n *\n * Back-fills {@link DEFAULT_LOGGER_MATCHER_OPTIONS} for direct callers of\n * {@link isLoggerCall} that pass partial or omitted options; rule callers\n * already receive fully-resolved options from `meta.defaultOptions`.\n */\nexport function buildLoggerMatcherSets(\n\toptions: LoggerMatcherOptions = {},\n): LoggerMatcherSets {\n\tconst resolved = { ...DEFAULT_LOGGER_MATCHER_OPTIONS, ...options };\n\treturn {\n\t\tallowedLoggerObjects: new Set(resolved.allowedLoggerObjects),\n\t\tallowedLoggerIdentifiers: new Set(resolved.allowedLoggerIdentifiers),\n\t\tattributesFirstLoggerObjects: new Set(\n\t\t\tresolved.attributesFirstLoggerObjects,\n\t\t),\n\t\tattributesFirstLoggerIdentifiers: new Set(\n\t\t\tresolved.attributesFirstLoggerIdentifiers,\n\t\t),\n\t\tlevelMethods: new Set(resolved.levelMethods),\n\t\tignoreDynamicLevelMethods: resolved.ignoreDynamicLevelMethods,\n\t};\n}\n\n// ─── Internal AST helpers ─────────────────────────────────────────────────────\n\n/**\n * Removes optional-chaining wrapper nodes so logger-call analysis can inspect\n * the underlying callee.\n */\nfunction unwrapChainExpression(\n\tnode: TSESTree.Node | null | undefined,\n): TSESTree.Node | null | undefined {\n\tif (node?.type === AST_NODE_TYPES.ChainExpression) {\n\t\treturn node.expression;\n\t}\n\treturn node;\n}\n\n/**\n * Reads a statically-known member property name from dot or string-literal\n * bracket notation.\n */\nfunction getStaticPropertyName(\n\tnode: TSESTree.Node | null | undefined,\n): string | null {\n\tif (node?.type !== AST_NODE_TYPES.MemberExpression) return null;\n\tif (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {\n\t\treturn node.property.name;\n\t}\n\tif (\n\t\tnode.computed &&\n\t\tnode.property.type === AST_NODE_TYPES.Literal &&\n\t\ttypeof node.property.value === \"string\"\n\t) {\n\t\treturn node.property.value;\n\t}\n\treturn null;\n}\n\n/**\n * Converts a non-computed member expression chain into a dotted path such as\n * `log.child`.\n */\nfunction getMemberPath(node: TSESTree.Node | null | undefined): string | null {\n\tconst segments: string[] = [];\n\tlet current: TSESTree.Node | null | undefined = node;\n\n\twhile (current) {\n\t\tif (current.type === AST_NODE_TYPES.Identifier) {\n\t\t\tsegments.unshift(current.name);\n\t\t\treturn segments.join(\".\");\n\t\t}\n\n\t\tif (current.type === AST_NODE_TYPES.MemberExpression && !current.computed) {\n\t\t\t// Non-computed member expressions (a.b) always have an Identifier property.\n\t\t\t// PrivateIdentifiers (#field) are not valid logger paths.\n\t\t\tif (current.property.type !== AST_NODE_TYPES.Identifier) return null;\n\t\t\tsegments.unshift(current.property.name);\n\t\t\tcurrent = current.object;\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\treturn null;\n}\n\n/**\n * Matches a call expression against the configured logger objects and returns\n * its expected argument positions.\n */\nfunction getLoggerCallConfig(\n\tnode: TSESTree.Node | null | undefined,\n\tsets: LoggerMatcherSets,\n): LoggerCallConfig | false {\n\tif (node?.type !== AST_NODE_TYPES.CallExpression) return false;\n\n\tconst callee = unwrapChainExpression(node.callee);\n\tif (callee?.type !== AST_NODE_TYPES.MemberExpression) return false;\n\n\tconst objectPath = getMemberPath(callee.object);\n\tconst objectIdentifier =\n\t\tcallee.object.type === AST_NODE_TYPES.Identifier\n\t\t\t? callee.object.name\n\t\t\t: null;\n\n\tconst isAllowedLoggerObject =\n\t\t(objectPath !== null && sets.allowedLoggerObjects.has(objectPath)) ||\n\t\t(objectIdentifier !== null &&\n\t\t\tsets.allowedLoggerIdentifiers.has(objectIdentifier));\n\n\tif (!isAllowedLoggerObject) return false;\n\n\tconst method = getStaticPropertyName(callee);\n\tconst isLevelMethod = method\n\t\t? sets.levelMethods.has(method)\n\t\t: callee.computed && !sets.ignoreDynamicLevelMethods;\n\n\tif (!isLevelMethod) return false;\n\n\tconst attributesFirst =\n\t\t(objectPath !== null &&\n\t\t\tsets.attributesFirstLoggerObjects.has(objectPath)) ||\n\t\t(objectIdentifier !== null &&\n\t\t\tsets.attributesFirstLoggerIdentifiers.has(objectIdentifier));\n\n\treturn {\n\t\tattributesArgumentIndex: attributesFirst ? 0 : 1,\n\t\tmessageArgumentIndex: attributesFirst ? 1 : 0,\n\t};\n}\n\n/**\n * Extracts a static object property key, rejecting computed keys that cannot be\n * checked safely by logger attribute rules.\n */\nfunction getPropertyKey(prop: TSESTree.Property): string | null {\n\tif (prop.computed) return null;\n\t// Widen to TSESTree.Node to avoid over-narrowing of the discriminated union.\n\tconst key = prop.key as TSESTree.Node;\n\tif (key.type === AST_NODE_TYPES.Identifier) return key.name;\n\tif (key.type === AST_NODE_TYPES.Literal) return String(key.value);\n\treturn null;\n}\n\n// ─── Public helpers ───────────────────────────────────────────────────────────\n\n/**\n * Convenience wrapper for external callers that don't pre-build sets.\n * Within this plugin's rules, prefer {@link getLoggerArguments} with\n * pre-built sets from {@link buildLoggerMatcherSets}.\n */\nexport function isLoggerCall(\n\tnode: TSESTree.Node | null | undefined,\n\toptions: LoggerMatcherOptions = {},\n): boolean {\n\treturn Boolean(getLoggerCallConfig(node, buildLoggerMatcherSets(options)));\n}\n\n/**\n * Returns the message and attributes arguments for a logger call, or `null`\n * if the node is not a recognised logger call. Use pre-built sets from\n * {@link buildLoggerMatcherSets} so sets are not reallocated per-node.\n */\nexport function getLoggerArguments(\n\tnode: TSESTree.CallExpression,\n\tsets: LoggerMatcherSets,\n): {\n\tattrsArg: TSESTree.Node | undefined;\n\tmessageArg: TSESTree.Node | undefined;\n} | null {\n\tconst config = getLoggerCallConfig(node, sets);\n\tif (!config) return null;\n\treturn {\n\t\tattrsArg: node.arguments[config.attributesArgumentIndex],\n\t\tmessageArg: node.arguments[config.messageArgumentIndex],\n\t};\n}\n\n/**\n * Extracts key/value pairs from an attribute object literal.\n * Returns `null` when the node cannot be analysed (not an ObjectExpression).\n * Returns `[]` when there is no attributes argument at all.\n */\nexport function collectAttrsEntries(\n\tattrsNode: TSESTree.Node | null | undefined,\n\toptions: CollectAttrsOptions = {},\n): AttrEntry[] | null {\n\tif (!attrsNode) return [];\n\n\tconst {\n\t\treportMalformedProperties = false,\n\t\treportNonLiteral = false,\n\t\treport,\n\t} = options;\n\n\tif (attrsNode.type !== AST_NODE_TYPES.ObjectExpression) {\n\t\tif (reportNonLiteral) {\n\t\t\treport?.(attrsNode, \"nonInlineAttributes\");\n\t\t}\n\t\treturn null;\n\t}\n\n\tconst entries: AttrEntry[] = [];\n\n\tfor (const prop of attrsNode.properties) {\n\t\tif (prop.type === AST_NODE_TYPES.SpreadElement) {\n\t\t\tif (reportMalformedProperties) {\n\t\t\t\treport?.(prop, \"noAttributeSpread\");\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// ObjectExpression.properties is Property | SpreadElement; after the\n\t\t// SpreadElement branch above, prop is always a Property here.\n\t\tconst key = getPropertyKey(prop);\n\t\tif (!key) {\n\t\t\tif (reportMalformedProperties) {\n\t\t\t\treport?.(prop.key, \"staticAttributeKeys\");\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tentries.push({\n\t\t\tkey,\n\t\t\tkeyNode: prop.key,\n\t\t\tvalueNode: prop.value as TSESTree.Expression,\n\t\t});\n\t}\n\n\treturn entries;\n}\n\n// ─── Value-shape helpers ──────────────────────────────────────────────────────\n\n/** Returns whether a node is a literal string, number, or boolean value. */\nfunction isScalarLiteral(node: TSESTree.Node | null | undefined): boolean {\n\treturn (\n\t\tnode?.type === AST_NODE_TYPES.Literal &&\n\t\t(typeof node.value === \"string\" ||\n\t\t\ttypeof node.value === \"number\" ||\n\t\t\ttypeof node.value === \"boolean\")\n\t);\n}\n\n/**\n * Returns whether a node is a syntactic expression shape known to produce a\n * scalar log attribute without requiring type information.\n */\nfunction isKnownScalarExpression(\n\tnode: TSESTree.Node | null | undefined,\n): boolean {\n\treturn isScalarLiteral(node) || node?.type === AST_NODE_TYPES.TemplateLiteral;\n}\n\n/**\n * Returns whether a single (non-array) attribute value should be rejected as a\n * primitive log value.\n *\n * @remarks\n * Objects, arrays, functions, classes, and non-scalar literals are always\n * rejected. Unknown expressions are rejected only when\n * `disallowUnknownAttributeValues` is `true`.\n */\nfunction isDisallowedPrimitiveValue(\n\tnode: TSESTree.Node | null | undefined,\n\toptions: { disallowUnknownAttributeValues?: boolean },\n): boolean {\n\tif (node?.type !== undefined && NON_PRIMITIVE_NODE_TYPES.has(node.type))\n\t\treturn true;\n\tif (node?.type === AST_NODE_TYPES.Literal) return !isScalarLiteral(node);\n\treturn (\n\t\toptions.disallowUnknownAttributeValues === true &&\n\t\t!isKnownScalarExpression(node)\n\t);\n}\n\n/**\n * Returns whether an attribute value should be rejected by primitive-value\n * rules.\n *\n * @remarks\n * Primitive values (string, number, boolean) are always allowed. Arrays are\n * allowed when every element is itself a primitive value, so arrays of scalars\n * such as `[\"a\", \"b\"]` pass while nested arrays and arrays of objects do not.\n * Objects, functions, and classes are always rejected. Unknown expressions\n * (including array spreads) are rejected only when\n * `disallowUnknownAttributeValues` is `true`.\n */\nexport function isDisallowedAttributeValue(\n\tnode: TSESTree.Node | null | undefined,\n\toptions: { disallowUnknownAttributeValues?: boolean } = {},\n): boolean {\n\tif (node?.type === AST_NODE_TYPES.ArrayExpression) {\n\t\treturn node.elements.some((element) => {\n\t\t\t// Array holes (`[1, , 3]`) parse as null elements; treat them as empty.\n\t\t\tif (element === null) return false;\n\t\t\tif (element.type === AST_NODE_TYPES.SpreadElement) {\n\t\t\t\treturn options.disallowUnknownAttributeValues === true;\n\t\t\t}\n\t\t\treturn isDisallowedPrimitiveValue(element, options);\n\t\t});\n\t}\n\treturn isDisallowedPrimitiveValue(node, options);\n}\n\n/**\n * Returns whether a candidate logger message is clearly not static text.\n *\n * @remarks\n * This intentionally returns `false` for a missing node so callers can report a\n * dedicated \"message required\" violation.\n */\nexport function isClearlyNotMessage(\n\tnode: TSESTree.Node | null | undefined,\n): boolean {\n\tif (!node) return false;\n\treturn node.type !== AST_NODE_TYPES.Literal || typeof node.value !== \"string\";\n}\n"],"mappings":"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"}