@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.
- package/README.md +204 -0
- package/index.js +176 -0
- 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>{ "type": ... }</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
|
+
}
|