eslint-plugin-crisp 1.6.2 → 1.6.4
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 +2 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/recommended-ts.js +1 -0
- package/recommended.js +1 -0
- package/rules/logger-arguments.js +149 -0
- package/rules/one-space-after-operator.js +39 -9
- package/tests/rules/logger-arguments.test.js +123 -0
- package/tests/rules/one-space-after-operator.test.js +48 -0
package/README.md
CHANGED
|
@@ -125,9 +125,10 @@ Each item has emojis denoting:
|
|
|
125
125
|
| [crisp/constructor-variables](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/constructor-variables.js) | Ensures all class properties in the constructor start with `_` | 🟠 |
|
|
126
126
|
| [crisp/enforce-optional](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/enforce-optional.js) | Enforces use of optional chaining | 🟠 | 🟢 | 🟣 |
|
|
127
127
|
| [crisp/header-check](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/header-check.js) | Enforces files to start with Crisp header | 🟠 | 🟢 | 🟣 |
|
|
128
|
-
| [crisp/header-comments-check](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/header-comments-check.js) | Enforces different comment blocks before different groups (imports, constants
|
|
128
|
+
| [crisp/header-comments-check](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/header-comments-check.js) | Enforces different comment blocks before different groups (imports, constants and instances) | 🟠 | 🟢 | 🟣 |
|
|
129
129
|
| [crisp/import-group-comment](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/import-group-comment.js) | Ensures `import` statements are preceded by a comment stating their type | | 🟢 | 🟣 |
|
|
130
130
|
| [crisp/import-group-order](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/import-group-order.js) | Ensures `import` statements are correctly ordered | | 🟢 | 🟣 |
|
|
131
|
+
| [crisp/logger-arguments](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/logger-arguments.js) | Requires static logger messages and reference-only interpolation arguments | 🟠 | 🟢 | 🟣 |
|
|
131
132
|
| [crisp/methods-naming](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/methods-naming.js) | Ensures methods are named according to their access (`public`, `private`, `protected`) | 🟠 | 🟢 | 🟣 |
|
|
132
133
|
| [crisp/methods-ordering](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/methods-ordering.js) | Ensures methods order according to their access: `public` then `protected` then `private` | 🟠 | 🟢 | 🟣 |
|
|
133
134
|
| [crisp/multiline-comment-end-backslash](https://github.com/crisp-oss/eslint-plugin-crisp/blob/master/rules/multiline-comment-end-backslash.js) | Enforces multiline comments to end with a backslash | 🟠 | 🟢 | 🟣 |
|
package/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import ruleJsdocEnforceClassdesc from "./rules/jsdoc-enforce-classdesc.js";
|
|
|
24
24
|
import ruleJsdocNoParamReturn from "./rules/jsdoc-no-param-return.js";
|
|
25
25
|
import ruleJsdocRequireDescriptionUppercase from "./rules/jsdoc-require-description-uppercase.js";
|
|
26
26
|
import ruleJsdocDescriptionLength from "./rules/jsdoc-description-length.js";
|
|
27
|
+
import ruleLoggerArguments from "./rules/logger-arguments.js";
|
|
27
28
|
import ruleMethodsNaming from "./rules/methods-naming.js";
|
|
28
29
|
import ruleMethodsOrdering from "./rules/methods-ordering.js";
|
|
29
30
|
import ruleMultilineCommentEndBackslash from "./rules/multiline-comment-end-backslash.js";
|
|
@@ -101,6 +102,7 @@ const plugin = {
|
|
|
101
102
|
"jsdoc-no-param-return": ruleJsdocNoParamReturn,
|
|
102
103
|
"jsdoc-require-description-uppercase": ruleJsdocRequireDescriptionUppercase,
|
|
103
104
|
"jsdoc-description-length": ruleJsdocDescriptionLength,
|
|
105
|
+
"logger-arguments": ruleLoggerArguments,
|
|
104
106
|
"methods-naming": ruleMethodsNaming,
|
|
105
107
|
"methods-ordering": ruleMethodsOrdering,
|
|
106
108
|
"multiline-comment-end-backslash": ruleMultilineCommentEndBackslash,
|
package/package.json
CHANGED
package/recommended-ts.js
CHANGED
|
@@ -161,6 +161,7 @@ export default function configRecommendedTS(pluginCrisp) {
|
|
|
161
161
|
"crisp/header-comments-check": "error",
|
|
162
162
|
"crisp/import-group-comment": "error",
|
|
163
163
|
"crisp/import-group-order": "error",
|
|
164
|
+
"crisp/logger-arguments": "error",
|
|
164
165
|
"crisp/methods-naming": "error",
|
|
165
166
|
"crisp/methods-ordering": "error",
|
|
166
167
|
"crisp/multiline-comment-end-backslash": "error",
|
package/recommended.js
CHANGED
|
@@ -166,6 +166,7 @@ export default function configRecommended(pluginCrisp) {
|
|
|
166
166
|
"crisp/enforce-optional": "error",
|
|
167
167
|
"crisp/header-check": "error",
|
|
168
168
|
"crisp/header-comments-check": "error",
|
|
169
|
+
"crisp/logger-arguments": "error",
|
|
169
170
|
"crisp/methods-naming": "error",
|
|
170
171
|
"crisp/methods-ordering": "error",
|
|
171
172
|
"crisp/multiline-comment-end-backslash": "error",
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const LOGGER_METHODS = new Set([
|
|
2
|
+
"debug",
|
|
3
|
+
"error",
|
|
4
|
+
"fatal",
|
|
5
|
+
"info",
|
|
6
|
+
"log",
|
|
7
|
+
"trace",
|
|
8
|
+
"warn"
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
function getPropertyName(node) {
|
|
12
|
+
if (!node || node.type !== "MemberExpression") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!node.computed && node.property.type === "Identifier") {
|
|
17
|
+
return node.property.name;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
node.computed &&
|
|
22
|
+
node.property.type === "Literal" &&
|
|
23
|
+
typeof node.property.value === "string"
|
|
24
|
+
) {
|
|
25
|
+
return node.property.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isLoggerCall(node) {
|
|
32
|
+
if (
|
|
33
|
+
node.callee.type !== "MemberExpression" ||
|
|
34
|
+
!LOGGER_METHODS.has(getPropertyName(node.callee))
|
|
35
|
+
) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
node.callee.object.type === "MemberExpression" &&
|
|
41
|
+
getPropertyName(node.callee.object) === "logger"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function unwrapTransparentExpression(node) {
|
|
46
|
+
while (
|
|
47
|
+
node &&
|
|
48
|
+
(
|
|
49
|
+
node.type === "ChainExpression" ||
|
|
50
|
+
node.type === "TSAsExpression" ||
|
|
51
|
+
node.type === "TSInstantiationExpression" ||
|
|
52
|
+
node.type === "TSNonNullExpression" ||
|
|
53
|
+
node.type === "TSTypeAssertion"
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
node = node.expression;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return node;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isReference(node) {
|
|
63
|
+
node = unwrapTransparentExpression(node);
|
|
64
|
+
|
|
65
|
+
if (!node) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
node.type === "Identifier" ||
|
|
71
|
+
node.type === "MetaProperty" ||
|
|
72
|
+
node.type === "Super" ||
|
|
73
|
+
node.type === "ThisExpression"
|
|
74
|
+
) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (node.type !== "MemberExpression" || !isReference(node.object)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!node.computed) {
|
|
83
|
+
return (
|
|
84
|
+
node.property.type === "Identifier" ||
|
|
85
|
+
node.property.type === "PrivateIdentifier"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const property = unwrapTransparentExpression(node.property);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
property.type === "Literal" ||
|
|
93
|
+
isReference(property)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default {
|
|
98
|
+
meta: {
|
|
99
|
+
type: "suggestion",
|
|
100
|
+
docs: {
|
|
101
|
+
description: "enforce static logger messages and reference arguments",
|
|
102
|
+
category: "Best Practices",
|
|
103
|
+
recommended: false
|
|
104
|
+
},
|
|
105
|
+
schema: [],
|
|
106
|
+
messages: {
|
|
107
|
+
message:
|
|
108
|
+
"Logger messages must be single-line, double-quoted string literals.",
|
|
109
|
+
reference:
|
|
110
|
+
"Logger interpolation arguments must be references, not computed expressions."
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
create(context) {
|
|
115
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
CallExpression(node) {
|
|
119
|
+
if (!isLoggerCall(node)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const [message, ...values] = node.arguments;
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
!message ||
|
|
127
|
+
message.type !== "Literal" ||
|
|
128
|
+
typeof message.value !== "string" ||
|
|
129
|
+
message.loc.start.line !== message.loc.end.line ||
|
|
130
|
+
!sourceCode.getText(message).startsWith("\"")
|
|
131
|
+
) {
|
|
132
|
+
context.report({
|
|
133
|
+
node: message || node,
|
|
134
|
+
messageId: "message"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const value of values) {
|
|
139
|
+
if (!isReference(value)) {
|
|
140
|
+
context.report({
|
|
141
|
+
node: value,
|
|
142
|
+
messageId: "reference"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
category: "Stylistic Issues",
|
|
7
7
|
recommended: false,
|
|
8
8
|
},
|
|
9
|
-
fixable: "
|
|
9
|
+
fixable: "code",
|
|
10
10
|
schema: [
|
|
11
11
|
{
|
|
12
12
|
type: "object",
|
|
@@ -27,7 +27,7 @@ export default {
|
|
|
27
27
|
const config = context.options[0] || {};
|
|
28
28
|
const checkColon = config.checkColon !== false;
|
|
29
29
|
|
|
30
|
-
const checkSpacing = (operatorToken) => {
|
|
30
|
+
const checkSpacing = (operatorToken, valueNode, indentationNode) => {
|
|
31
31
|
if (!operatorToken) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
@@ -46,6 +46,10 @@ export default {
|
|
|
46
46
|
);
|
|
47
47
|
const hasSpaceBefore = /[ \t]$/.test(beforeText);
|
|
48
48
|
const hasOneSpaceAfter = afterText === " ";
|
|
49
|
+
const hasLineBreakAfter = (
|
|
50
|
+
/^[\s]*$/.test(afterText) &&
|
|
51
|
+
/[\r\n]/.test(afterText)
|
|
52
|
+
);
|
|
49
53
|
|
|
50
54
|
if (!hasSpaceBefore || !hasOneSpaceAfter) {
|
|
51
55
|
context.report({
|
|
@@ -62,11 +66,37 @@ export default {
|
|
|
62
66
|
));
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
if (!hasOneSpaceAfter
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
if (!hasOneSpaceAfter) {
|
|
70
|
+
if (/^[ \t]*$/.test(afterText)) {
|
|
71
|
+
fixes.push(fixer.replaceTextRange(
|
|
72
|
+
[operatorToken.range[1], tokenAfter.range[0]],
|
|
73
|
+
" "
|
|
74
|
+
));
|
|
75
|
+
} else if (hasLineBreakAfter && valueNode) {
|
|
76
|
+
const firstValueToken = sourceCode.getTokenAfter(operatorToken);
|
|
77
|
+
|
|
78
|
+
if (firstValueToken.value === "(") {
|
|
79
|
+
fixes.push(fixer.replaceTextRange(
|
|
80
|
+
[operatorToken.range[1], tokenAfter.range[0]],
|
|
81
|
+
" "
|
|
82
|
+
));
|
|
83
|
+
} else {
|
|
84
|
+
const indentation = " ".repeat(
|
|
85
|
+
indentationNode.loc.start.column
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
fixes.push(
|
|
89
|
+
fixer.replaceTextRange(
|
|
90
|
+
[operatorToken.range[1], tokenAfter.range[0]],
|
|
91
|
+
` (${afterText}`
|
|
92
|
+
),
|
|
93
|
+
fixer.insertTextAfter(
|
|
94
|
+
valueNode,
|
|
95
|
+
`\n${indentation})`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
return fixes.length ? fixes : null;
|
|
@@ -83,7 +113,7 @@ export default {
|
|
|
83
113
|
token => token.value === node.operator
|
|
84
114
|
);
|
|
85
115
|
|
|
86
|
-
checkSpacing(operatorToken);
|
|
116
|
+
checkSpacing(operatorToken, node.right, node);
|
|
87
117
|
},
|
|
88
118
|
VariableDeclarator(node) {
|
|
89
119
|
if (node.init) {
|
|
@@ -93,7 +123,7 @@ export default {
|
|
|
93
123
|
token => token.value === "="
|
|
94
124
|
);
|
|
95
125
|
|
|
96
|
-
checkSpacing(operatorToken);
|
|
126
|
+
checkSpacing(operatorToken, node.init, node.parent);
|
|
97
127
|
}
|
|
98
128
|
},
|
|
99
129
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import rule from "../../rules/logger-arguments.js";
|
|
2
|
+
import createRuleTester from "../rule-tester.js";
|
|
3
|
+
|
|
4
|
+
const ruleTester = createRuleTester();
|
|
5
|
+
|
|
6
|
+
ruleTester.run("logger-arguments", rule, {
|
|
7
|
+
valid: [
|
|
8
|
+
"App.logger.info(\"Application ready.\");",
|
|
9
|
+
"App.logger.debug(\"Found %s jobs at %s/%s\", cronJobs.length, JOBS_PATH, cronName);",
|
|
10
|
+
"app.logger.warn(\"Request failed\", error);",
|
|
11
|
+
"this._app.logger.error(\"Request failed: %s\", this.error.message);",
|
|
12
|
+
"App[\"logger\"][\"info\"](\"Loaded value: %s\", values[index]);",
|
|
13
|
+
`App.logger.info(
|
|
14
|
+
"Loaded value: %s",
|
|
15
|
+
value
|
|
16
|
+
);`,
|
|
17
|
+
"App.logger.info(\"This intentionally long logger message may exceed the usual source line length when necessary.\");",
|
|
18
|
+
"App.logger.child({ namespace: \"worker\" });",
|
|
19
|
+
"console.info(`Unrelated logger calls are ignored: ${value}`);"
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
invalid: [
|
|
23
|
+
{
|
|
24
|
+
code: "App.logger.info('Application ready.');",
|
|
25
|
+
errors: [
|
|
26
|
+
{
|
|
27
|
+
messageId: "message"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: "App.logger.info(`Application ready.`);",
|
|
33
|
+
errors: [
|
|
34
|
+
{
|
|
35
|
+
messageId: "message"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
code: "App.logger.info(`Loaded value: ${value}`);",
|
|
41
|
+
errors: [
|
|
42
|
+
{
|
|
43
|
+
messageId: "message"
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
code: "App.logger.info(\"Loaded \" + value);",
|
|
49
|
+
errors: [
|
|
50
|
+
{
|
|
51
|
+
messageId: "message"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: "App.logger.info(message);",
|
|
57
|
+
errors: [
|
|
58
|
+
{
|
|
59
|
+
messageId: "message"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: "App.logger.info();",
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
messageId: "message"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
code: "App.logger.debug(\"Loaded values: %s\", values.join(\", \"));",
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
messageId: "reference"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
code: "App.logger.debug(\"Loaded value: %s\", value || \"default\");",
|
|
81
|
+
errors: [
|
|
82
|
+
{
|
|
83
|
+
messageId: "reference"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
code: "App.logger.debug(\"Loaded value: %s\", JSON.stringify(value));",
|
|
89
|
+
errors: [
|
|
90
|
+
{
|
|
91
|
+
messageId: "reference"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
code: "App.logger.debug(\"Loaded value: %s\", { value });",
|
|
97
|
+
errors: [
|
|
98
|
+
{
|
|
99
|
+
messageId: "reference"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
code: "App.logger.debug(\"Retry count: %s\", 3);",
|
|
105
|
+
errors: [
|
|
106
|
+
{
|
|
107
|
+
messageId: "reference"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
code: "App.logger.debug(\"Values: %s / %s\", values.join(\", \"), fallback || \"none\");",
|
|
113
|
+
errors: [
|
|
114
|
+
{
|
|
115
|
+
messageId: "reference"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
messageId: "reference"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
});
|
|
@@ -68,6 +68,54 @@ ruleTester.run("one-space-after-operator", rule, {
|
|
|
68
68
|
}
|
|
69
69
|
]
|
|
70
70
|
},
|
|
71
|
+
{
|
|
72
|
+
code: `let value =
|
|
73
|
+
first ||
|
|
74
|
+
second;`,
|
|
75
|
+
output: `let value = (
|
|
76
|
+
first ||
|
|
77
|
+
second
|
|
78
|
+
);`,
|
|
79
|
+
errors: [
|
|
80
|
+
{
|
|
81
|
+
message: "There should be at least one space before and exactly one space after '='"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
code: `function update() {
|
|
87
|
+
value +=
|
|
88
|
+
first *
|
|
89
|
+
second;
|
|
90
|
+
}`,
|
|
91
|
+
output: `function update() {
|
|
92
|
+
value += (
|
|
93
|
+
first *
|
|
94
|
+
second
|
|
95
|
+
);
|
|
96
|
+
}`,
|
|
97
|
+
errors: [
|
|
98
|
+
{
|
|
99
|
+
message: "There should be at least one space before and exactly one space after '+='"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
code: `let value =
|
|
105
|
+
(
|
|
106
|
+
first ||
|
|
107
|
+
second
|
|
108
|
+
);`,
|
|
109
|
+
output: `let value = (
|
|
110
|
+
first ||
|
|
111
|
+
second
|
|
112
|
+
);`,
|
|
113
|
+
errors: [
|
|
114
|
+
{
|
|
115
|
+
message: "There should be at least one space before and exactly one space after '='"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
71
119
|
{
|
|
72
120
|
code: "const object = { key: value };",
|
|
73
121
|
output: "const object = { key : value };",
|