@mimik/eslint-plugin-logger 1.0.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 (3) hide show
  1. package/README.md +204 -0
  2. package/index.js +176 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # @mimik/eslint-plugin-logger
2
+
3
+ ESLint plugin that validates logger call arguments for mimik services.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev @mimik/eslint-plugin-logger
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Add the plugin to your `eslint.config.js`:
14
+
15
+ ```js
16
+ import loggerPlugin from '@mimik/eslint-plugin-logger';
17
+
18
+ export default [
19
+ {
20
+ plugins: { logger: loggerPlugin },
21
+ rules: {
22
+ 'logger/validate-logger-args': 'error',
23
+ },
24
+ },
25
+ ];
26
+ ```
27
+
28
+ ## Rule: `validate-logger-args`
29
+
30
+ Validates that logger calls (on default-imported modules) use the correct argument signatures.
31
+
32
+ ### Valid Signatures
33
+
34
+ ```js
35
+ import logger from '@mimik/sumologic-winston-logger';
36
+
37
+ // (message, correlationId)
38
+ logger.info('User logged in', correlationId);
39
+
40
+ // (message, meta, correlationId)
41
+ logger.info('User logged in', { userId: 123 }, correlationId);
42
+
43
+ // (message, correlationId, options)
44
+ logger.info('User logged in', correlationId, { type: 'audit' });
45
+
46
+ // (message, meta, correlationId, options)
47
+ logger.info('User logged in', { userId: 123 }, correlationId, { type: 'audit' });
48
+ ```
49
+
50
+ ### Arguments
51
+
52
+ | Argument | Type | Required | Description |
53
+ |---|---|---|---|
54
+ | `message` | `string` | Yes | Log message |
55
+ | `meta` | `object` | No | Metadata object with additional context |
56
+ | `correlationId` | `string` | Yes | Correlation ID for request tracing |
57
+ | `options` | `object` | No | Options object, must contain a `type` string property |
58
+
59
+ ### Supported Log Levels
60
+
61
+ `error`, `warn`, `info`, `verbose`, `debug`, `silly`
62
+
63
+ ### What Gets Checked
64
+
65
+ - The rule only applies to **default imports** (e.g. `import logger from '...'`)
66
+ - Named imports (`import { logger } from '...'`) are ignored
67
+ - Minimum 2 arguments, maximum 4
68
+ - `message` must be a string (when passed as a literal)
69
+ - `correlationId` must be a string (when passed as a literal)
70
+ - `options` object must contain a `type` property with a string value
71
+ - When 3 arguments are passed, the 2nd argument type determines the signature:
72
+ - Object literal → `(message, meta, correlationId)`
73
+ - Otherwise → `(message, correlationId, options)`
74
+
75
+ ## API Reference
76
+
77
+ ## Constants
78
+
79
+ <dl>
80
+ <dt><a href="#plugin">plugin</a> : <code>Object</code></dt>
81
+ <dd><p>ESLint plugin for validating logger call arguments.</p>
82
+ </dd>
83
+ </dl>
84
+
85
+ ## Functions
86
+
87
+ <dl>
88
+ <dt><a href="#isNonStringLiteral">isNonStringLiteral(node)</a> ⇒ <code>boolean</code></dt>
89
+ <dd><p>Check whether an AST node is a non-string literal (number, boolean, null).</p>
90
+ </dd>
91
+ <dt><a href="#isObjectNode">isObjectNode(node)</a> ⇒ <code>boolean</code></dt>
92
+ <dd><p>Check whether an AST node is an object expression.</p>
93
+ </dd>
94
+ <dt><a href="#isTypeKey">isTypeKey(prop)</a> ⇒ <code>boolean</code></dt>
95
+ <dd><p>Check whether a property node has the key <code>type</code> (identifier or string literal).</p>
96
+ </dd>
97
+ <dt><a href="#findTypeProperty">findTypeProperty(node)</a> ⇒ <code>Object</code> | <code>undefined</code></dt>
98
+ <dd><p>Find the <code>type</code> property node inside an ObjectExpression.
99
+ Handles both identifier keys (<code>{ type: ... }</code>) and string-literal keys (<code>{ &quot;type&quot;: ... }</code>).</p>
100
+ </dd>
101
+ <dt><a href="#validateOptions">validateOptions(context, optionsArg, level)</a></dt>
102
+ <dd><p>Validate the options argument of a logger call.
103
+ Reports an error when the options object is missing a <code>type</code> property or when the
104
+ <code>type</code> value is a non-string literal. Variable references (e.g. <code>{ type: someVar }</code>)
105
+ are allowed because their runtime type cannot be determined statically.</p>
106
+ </dd>
107
+ <dt><a href="#validateStringArg">validateStringArg(context, argNode, level, messageId)</a></dt>
108
+ <dd><p>Validate that a logger argument is a string when it is a literal.
109
+ Non-literal nodes (identifiers, call expressions) are skipped because their
110
+ runtime type cannot be determined statically.</p>
111
+ </dd>
112
+ </dl>
113
+
114
+ <a name="plugin"></a>
115
+
116
+ ## plugin : <code>Object</code>
117
+ ESLint plugin for validating logger call arguments.
118
+
119
+ **Kind**: global constant
120
+ <a name="isNonStringLiteral"></a>
121
+
122
+ ## isNonStringLiteral(node) ⇒ <code>boolean</code>
123
+ Check whether an AST node is a non-string literal (number, boolean, null).
124
+
125
+ **Kind**: global function
126
+ **Returns**: <code>boolean</code> - True when the node is a literal whose value is not a string.
127
+
128
+ | Param | Type | Description |
129
+ | --- | --- | --- |
130
+ | node | <code>Object</code> | The AST node to check. |
131
+
132
+ <a name="isObjectNode"></a>
133
+
134
+ ## isObjectNode(node) ⇒ <code>boolean</code>
135
+ Check whether an AST node is an object expression.
136
+
137
+ **Kind**: global function
138
+ **Returns**: <code>boolean</code> - True when the node is an ObjectExpression.
139
+
140
+ | Param | Type | Description |
141
+ | --- | --- | --- |
142
+ | node | <code>Object</code> | The AST node to check. |
143
+
144
+ <a name="isTypeKey"></a>
145
+
146
+ ## isTypeKey(prop) ⇒ <code>boolean</code>
147
+ Check whether a property node has the key `type` (identifier or string literal).
148
+
149
+ **Kind**: global function
150
+ **Returns**: <code>boolean</code> - True when the key is `type`.
151
+
152
+ | Param | Type | Description |
153
+ | --- | --- | --- |
154
+ | prop | <code>Object</code> | The property node. |
155
+
156
+ <a name="findTypeProperty"></a>
157
+
158
+ ## findTypeProperty(node) ⇒ <code>Object</code> \| <code>undefined</code>
159
+ Find the `type` property node inside an ObjectExpression.
160
+ Handles both identifier keys (`{ type: ... }`) and string-literal keys (`{ "type": ... }`).
161
+
162
+ **Kind**: global function
163
+ **Returns**: <code>Object</code> \| <code>undefined</code> - The property node, or undefined if not found.
164
+
165
+ | Param | Type | Description |
166
+ | --- | --- | --- |
167
+ | node | <code>Object</code> | The object expression node. |
168
+
169
+ <a name="validateOptions"></a>
170
+
171
+ ## validateOptions(context, optionsArg, level)
172
+ Validate the options argument of a logger call.
173
+ Reports an error when the options object is missing a `type` property or when the
174
+ `type` value is a non-string literal. Variable references (e.g. `{ type: someVar }`)
175
+ are allowed because their runtime type cannot be determined statically.
176
+
177
+ **Kind**: global function
178
+
179
+ | Param | Type | Description |
180
+ | --- | --- | --- |
181
+ | context | <code>Object</code> | The ESLint rule context. |
182
+ | optionsArg | <code>Object</code> | The options argument AST node. |
183
+ | level | <code>string</code> | The logger level (e.g. 'info', 'error'). |
184
+
185
+ <a name="validateStringArg"></a>
186
+
187
+ ## validateStringArg(context, argNode, level, messageId)
188
+ Validate that a logger argument is a string when it is a literal.
189
+ Non-literal nodes (identifiers, call expressions) are skipped because their
190
+ runtime type cannot be determined statically.
191
+
192
+ **Kind**: global function
193
+
194
+ | Param | Type | Description |
195
+ | --- | --- | --- |
196
+ | context | <code>Object</code> | The ESLint rule context. |
197
+ | argNode | <code>Object</code> | The argument AST node. |
198
+ | level | <code>string</code> | The logger level (e.g. 'info', 'error'). |
199
+ | messageId | <code>string</code> | The ESLint message ID to report on failure. |
200
+
201
+
202
+ ## License
203
+
204
+ MIT
package/index.js ADDED
@@ -0,0 +1,176 @@
1
+ const LEVELS = new Set(['error', 'warn', 'info', 'verbose', 'debug', 'silly']);
2
+ const MIN_ARGS = 2;
3
+ const THREE_ARGS = 3;
4
+ const MAX_ARGS = 4;
5
+
6
+ /**
7
+ * Check whether an AST node is a non-string literal (number, boolean, null).
8
+ * @param {Object} node - The AST node to check.
9
+ * @returns {boolean} True when the node is a literal whose value is not a string.
10
+ */
11
+ const isNonStringLiteral = node => node.type === 'Literal' && typeof node.value !== 'string';
12
+
13
+ /**
14
+ * Check whether an AST node is an object expression.
15
+ * @param {Object} node - The AST node to check.
16
+ * @returns {boolean} True when the node is an ObjectExpression.
17
+ */
18
+ const isObjectNode = node => node.type === 'ObjectExpression';
19
+
20
+ /**
21
+ * Check whether a property node has the key `type` (identifier or string literal).
22
+ * @param {Object} prop - The property node.
23
+ * @returns {boolean} True when the key is `type`.
24
+ */
25
+ const isTypeKey = prop => prop.type === 'Property'
26
+ && !prop.computed
27
+ && ((prop.key.type === 'Identifier' && prop.key.name === 'type')
28
+ || (prop.key.type === 'Literal' && prop.key.value === 'type'));
29
+
30
+ /**
31
+ * Find the `type` property node inside an ObjectExpression.
32
+ * Handles both identifier keys (`{ type: ... }`) and string-literal keys (`{ "type": ... }`).
33
+ * @param {Object} node - The object expression node.
34
+ * @returns {Object|undefined} The property node, or undefined if not found.
35
+ */
36
+ const findTypeProperty = node => node.properties.find(isTypeKey);
37
+
38
+ /**
39
+ * Validate the options argument of a logger call.
40
+ * Reports an error when the options object is missing a `type` property or when the
41
+ * `type` value is a non-string literal. Variable references (e.g. `{ type: someVar }`)
42
+ * are allowed because their runtime type cannot be determined statically.
43
+ * @param {Object} context - The ESLint rule context.
44
+ * @param {Object} optionsArg - The options argument AST node.
45
+ * @param {string} level - The logger level (e.g. 'info', 'error').
46
+ */
47
+ const validateOptions = (context, optionsArg, level) => {
48
+ if (!isObjectNode(optionsArg)) return;
49
+ const typeProp = findTypeProperty(optionsArg);
50
+ if (!typeProp) {
51
+ context.report({
52
+ node: optionsArg,
53
+ messageId: 'optionsMissingType',
54
+ data: { level },
55
+ });
56
+ }
57
+ else if (isNonStringLiteral(typeProp.value)) {
58
+ context.report({
59
+ node: optionsArg,
60
+ messageId: 'optionsTypeNotString',
61
+ data: { level },
62
+ });
63
+ }
64
+ };
65
+
66
+ /**
67
+ * Validate that a logger argument is a string when it is a literal.
68
+ * Non-literal nodes (identifiers, call expressions) are skipped because their
69
+ * runtime type cannot be determined statically.
70
+ * @param {Object} context - The ESLint rule context.
71
+ * @param {Object} argNode - The argument AST node.
72
+ * @param {string} level - The logger level (e.g. 'info', 'error').
73
+ * @param {string} messageId - The ESLint message ID to report on failure.
74
+ */
75
+ const validateStringArg = (context, argNode, level, messageId) => {
76
+ if (isNonStringLiteral(argNode)) {
77
+ context.report({
78
+ node: argNode,
79
+ messageId,
80
+ data: { level, actualType: typeof argNode.value },
81
+ });
82
+ }
83
+ };
84
+
85
+ /**
86
+ * ESLint plugin for validating logger call arguments.
87
+ * @type {Object}
88
+ */
89
+ const plugin = {
90
+ rules: {
91
+ 'validate-logger-args': {
92
+ meta: {
93
+ type: 'problem',
94
+ docs: {
95
+ category: 'Logging',
96
+ description: 'Validate logger call arguments match the required signature: (message, correlationId), (message, meta, correlationId), (message, correlationId, options), or (message, meta, correlationId, options).',
97
+ },
98
+ schema: [],
99
+ messages: {
100
+ tooFewArguments: 'logger.{{ level }}() requires at least 2 arguments: (message, correlationId). Found {{ count }}.',
101
+ tooManyArguments: 'logger.{{ level }}() accepts at most 4 arguments: (message, meta, correlationId, options). Found {{ count }}.',
102
+ messageNotString: 'logger.{{ level }}() message (1st argument) must be a string. Found {{ actualType }}.',
103
+ correlationIdNotString: 'logger.{{ level }}() correlationId must be a string. Found {{ actualType }}.',
104
+ optionsMissingType: 'logger.{{ level }}() options object must contain a "type" property.',
105
+ optionsTypeNotString: 'logger.{{ level }}() options "type" property must be a string.',
106
+ },
107
+ },
108
+ create(context) {
109
+ const defaultImports = new Set();
110
+
111
+ return {
112
+ ImportDefaultSpecifier(node) {
113
+ defaultImports.add(node.local.name);
114
+ },
115
+
116
+ CallExpression(node) {
117
+ if (node.callee.type !== 'MemberExpression') return;
118
+ if (node.callee.object.type !== 'Identifier') return;
119
+ if (!defaultImports.has(node.callee.object.name)) return;
120
+ if (node.callee.computed) return;
121
+
122
+ const level = node.callee.property.name;
123
+ if (!LEVELS.has(level)) return;
124
+
125
+ const args = node.arguments;
126
+
127
+ if (args.length < MIN_ARGS) {
128
+ context.report({
129
+ node,
130
+ messageId: 'tooFewArguments',
131
+ data: { level, count: String(args.length) },
132
+ });
133
+ return;
134
+ }
135
+
136
+ if (args.length > MAX_ARGS) {
137
+ context.report({
138
+ node,
139
+ messageId: 'tooManyArguments',
140
+ data: { level, count: String(args.length) },
141
+ });
142
+ return;
143
+ }
144
+
145
+ // Validate message (always 1st argument)
146
+ validateStringArg(context, args[0], level, 'messageNotString');
147
+
148
+ if (args.length === MIN_ARGS) {
149
+ // (message, correlationId)
150
+ validateStringArg(context, args[1], level, 'correlationIdNotString');
151
+ }
152
+ else if (args.length === THREE_ARGS) {
153
+ // Disambiguate: (message, meta, correlationId) vs (message, correlationId, options)
154
+ if (isObjectNode(args[1])) {
155
+ // (message, meta, correlationId)
156
+ validateStringArg(context, args[MIN_ARGS], level, 'correlationIdNotString');
157
+ }
158
+ else {
159
+ // (message, correlationId, options)
160
+ validateStringArg(context, args[1], level, 'correlationIdNotString');
161
+ validateOptions(context, args[MIN_ARGS], level);
162
+ }
163
+ }
164
+ else if (args.length === MAX_ARGS) {
165
+ // (message, meta, correlationId, options)
166
+ validateStringArg(context, args[MIN_ARGS], level, 'correlationIdNotString');
167
+ validateOptions(context, args[THREE_ARGS], level);
168
+ }
169
+ },
170
+ };
171
+ },
172
+ },
173
+ },
174
+ };
175
+
176
+ export default plugin;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@mimik/eslint-plugin-logger",
3
+ "version": "1.0.0",
4
+ "description": "validation of the logger parameters for mimik",
5
+ "main": "./index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "lint": "eslint . --no-error-on-unmatched-pattern",
9
+ "docs": "jsdoc2md --template docs/README.hbs index.js > README.md",
10
+ "test": "mocha --reporter mochawesome test/ --recursive",
11
+ "test-ci": "c8 --reporter=lcov --reporter=text npm test",
12
+ "prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
13
+ "commit-ready": "npm run docs && npm run lint && npm run test-ci"
14
+ },
15
+ "keywords": [
16
+ "mimik",
17
+ "eslint",
18
+ "eslint-plugin",
19
+ "eslintplugin",
20
+ "logger"
21
+ ],
22
+ "author": "mimik technology inc <support@mimik.com> (https://developer.mimik.com/)",
23
+ "license": "MIT",
24
+ "engines": {
25
+ "node": ">=24"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://bitbucket.org/mimiktech/eslint-plugin-logger.git"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "9.39.3",
33
+ "@stylistic/eslint-plugin": "5.10.0",
34
+ "c8": "11.0.0",
35
+ "eslint": "9.39.3",
36
+ "eslint-plugin-import": "2.32.0",
37
+ "globals": "17.4.0",
38
+ "husky": "9.1.7",
39
+ "jsdoc-to-markdown": "9.1.3",
40
+ "mocha": "11.7.5",
41
+ "mochawesome": "7.1.4"
42
+ }
43
+ }