eslint-plugin-th-rules 1.15.4 → 1.15.6
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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/rules/top-level-functions.js +151 -35
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.15.6](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.5...v1.15.6) (2024-12-30)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* enhance top-level functions rule to support async and export keywords ([cca9ceb](https://github.com/tomerh2001/eslint-plugin-th-rules/commit/cca9cebe77fa01820048a55c444e55bd297b883c))
|
|
7
|
+
|
|
8
|
+
## [1.15.5](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.4...v1.15.5) (2024-12-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* enforce naming conventions for top-level functions and improve error messages ([c50194d](https://github.com/tomerh2001/eslint-plugin-th-rules/commit/c50194d3ad4c2ef98387d3bbee8d06a30e2aa458))
|
|
14
|
+
|
|
1
15
|
## [1.15.4](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.3...v1.15.4) (2024-12-30)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prefer-module */
|
|
2
|
+
|
|
1
3
|
const meta = {
|
|
2
4
|
type: 'suggestion',
|
|
3
5
|
docs: {
|
|
@@ -10,45 +12,140 @@ const meta = {
|
|
|
10
12
|
schema: [],
|
|
11
13
|
};
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Build a replacement code string for an arrow function:
|
|
17
|
+
*
|
|
18
|
+
* @param {string} funcName - The name of the new function.
|
|
19
|
+
* @param {ArrowFunctionExpression} arrowNode - The ArrowFunctionExpression node.
|
|
20
|
+
* @param {import('eslint').SourceCode} sourceCode - The ESLint SourceCode object.
|
|
21
|
+
* @param {boolean} isExport - Whether or not this function is exported (e.g., `export const foo = ...`).
|
|
22
|
+
* @returns {string} The replacement code.
|
|
23
|
+
*/
|
|
24
|
+
function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isExport) {
|
|
25
|
+
const asyncKeyword = arrowNode.async ? 'async ' : '';
|
|
26
|
+
const exportKeyword = isExport ? 'export ' : '';
|
|
27
|
+
|
|
28
|
+
const parametersText = arrowNode.params.map(parameter => sourceCode.getText(parameter)).join(', ');
|
|
29
|
+
|
|
30
|
+
let bodyText;
|
|
31
|
+
if (arrowNode.body.type === 'BlockStatement') {
|
|
32
|
+
bodyText = sourceCode.getText(arrowNode.body);
|
|
33
|
+
} else {
|
|
34
|
+
const expressionText = sourceCode.getText(arrowNode.body);
|
|
35
|
+
bodyText = `{ return ${expressionText}; }`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `${exportKeyword}${asyncKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build a replacement code string for a function expression:
|
|
43
|
+
*
|
|
44
|
+
* @param {string} funcName - The name of the new function.
|
|
45
|
+
* @param {FunctionExpression} funcExprNode - The FunctionExpression node.
|
|
46
|
+
* @param {import('eslint').SourceCode} sourceCode - The ESLint SourceCode object.
|
|
47
|
+
* @param {boolean} isExport - Whether or not this function is exported.
|
|
48
|
+
* @returns {string} The replacement code.
|
|
49
|
+
*/
|
|
50
|
+
function buildFunctionExpressionReplacement(functionName, functionExprNode, sourceCode, isExport) {
|
|
51
|
+
const asyncKeyword = functionExprNode.async ? 'async ' : '';
|
|
52
|
+
const exportKeyword = isExport ? 'export ' : '';
|
|
53
|
+
|
|
54
|
+
const parametersText = functionExprNode.params.map(parameter => sourceCode.getText(parameter)).join(', ');
|
|
55
|
+
const bodyText = sourceCode.getText(functionExprNode.body);
|
|
56
|
+
|
|
57
|
+
return `${exportKeyword}${asyncKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build a replacement for an anonymous top-level FunctionDeclaration (including async).
|
|
62
|
+
*
|
|
63
|
+
* @param {import('eslint').SourceCode} sourceCode
|
|
64
|
+
* @param {import('estree').FunctionDeclaration} node
|
|
65
|
+
* @param {string} [funcName='defaultFunction']
|
|
66
|
+
* @param {boolean} [isExport=false]
|
|
67
|
+
*/
|
|
68
|
+
function buildAnonymousFunctionDeclarationReplacement(sourceCode, node, functionName = 'defaultFunction', isExport = false) {
|
|
69
|
+
const originalText = sourceCode.getText(node);
|
|
70
|
+
const asyncKeyword = node.async ? 'async ' : '';
|
|
71
|
+
const exportKeyword = isExport ? 'export ' : '';
|
|
72
|
+
|
|
73
|
+
let replaced = originalText;
|
|
74
|
+
const asyncFunctionRegex = /^\s*async\s+function\s*\(/;
|
|
75
|
+
const functionRegex = /^\s*function\s*\(/;
|
|
76
|
+
|
|
77
|
+
replaced = asyncFunctionRegex.test(replaced) ? replaced.replace(asyncFunctionRegex, `async function ${functionName}(`) : replaced.replace(functionRegex, `function ${functionName}(`);
|
|
78
|
+
|
|
79
|
+
if (isExport && !replaced.trimStart().startsWith('export')) {
|
|
80
|
+
replaced = `${exportKeyword}${replaced}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return replaced;
|
|
84
|
+
}
|
|
85
|
+
|
|
13
86
|
function create(context) {
|
|
87
|
+
const sourceCode = context.getSourceCode();
|
|
88
|
+
|
|
14
89
|
return {
|
|
15
90
|
VariableDeclarator(node) {
|
|
16
|
-
|
|
91
|
+
const declParent = node.parent;
|
|
92
|
+
const grandParent = declParent.parent;
|
|
93
|
+
|
|
94
|
+
const isTopLevel
|
|
95
|
+
= grandParent.type === 'Program'
|
|
96
|
+
|| grandParent.type === 'ExportNamedDeclaration'
|
|
97
|
+
|| grandParent.type === 'ExportDefaultDeclaration';
|
|
98
|
+
|
|
99
|
+
if (!isTopLevel) {
|
|
17
100
|
return;
|
|
18
101
|
}
|
|
19
102
|
|
|
20
|
-
const
|
|
103
|
+
const isExport
|
|
104
|
+
= grandParent.type === 'ExportNamedDeclaration'
|
|
105
|
+
|| grandParent.type === 'ExportDefaultDeclaration';
|
|
21
106
|
|
|
22
|
-
if (node.init
|
|
23
|
-
|
|
24
|
-
|
|
107
|
+
if (!node.init) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const functionName = node.id && node.id.name;
|
|
112
|
+
if (!functionName) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
25
115
|
|
|
116
|
+
if (node.init.type === 'ArrowFunctionExpression') {
|
|
26
117
|
context.report({
|
|
27
118
|
node: node.init,
|
|
28
|
-
message: 'Top-level functions must be named/regular functions.',
|
|
119
|
+
message: 'Top-level arrow functions must be named/regular functions.',
|
|
29
120
|
fix(fixer) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return fixer.replaceText(
|
|
121
|
+
const replacement = buildArrowFunctionReplacement(
|
|
122
|
+
functionName,
|
|
123
|
+
node.init,
|
|
124
|
+
sourceCode,
|
|
125
|
+
isExport,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return fixer.replaceText(
|
|
129
|
+
isExport ? grandParent : declParent,
|
|
130
|
+
replacement,
|
|
131
|
+
);
|
|
38
132
|
},
|
|
39
133
|
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (node.init && node.init.type === 'FunctionExpression') {
|
|
43
|
-
const functionName = node.id.name;
|
|
44
|
-
const functionText = sourceCode.getText(node.init);
|
|
45
|
-
|
|
134
|
+
} else if (node.init.type === 'FunctionExpression') {
|
|
46
135
|
context.report({
|
|
47
136
|
node: node.init,
|
|
48
|
-
message: 'Top-level
|
|
137
|
+
message: 'Top-level function expressions must be named/regular functions.',
|
|
49
138
|
fix(fixer) {
|
|
50
|
-
const
|
|
51
|
-
|
|
139
|
+
const replacement = buildFunctionExpressionReplacement(
|
|
140
|
+
functionName,
|
|
141
|
+
node.init,
|
|
142
|
+
sourceCode,
|
|
143
|
+
isExport,
|
|
144
|
+
);
|
|
145
|
+
return fixer.replaceText(
|
|
146
|
+
isExport ? grandParent : declParent,
|
|
147
|
+
replacement,
|
|
148
|
+
);
|
|
52
149
|
},
|
|
53
150
|
});
|
|
54
151
|
}
|
|
@@ -59,20 +156,39 @@ function create(context) {
|
|
|
59
156
|
return;
|
|
60
157
|
}
|
|
61
158
|
|
|
62
|
-
|
|
63
|
-
context.report({
|
|
64
|
-
node,
|
|
65
|
-
message: 'Top-level functions must be named.',
|
|
66
|
-
fix(fixer) {
|
|
67
|
-
const functionName = 'defaultFunction';
|
|
68
|
-
const sourceCode = context.getSourceCode();
|
|
69
|
-
const functionText = sourceCode.getText(node);
|
|
70
|
-
const fixedCode = functionText.replace('function (', `function ${functionName}(`);
|
|
159
|
+
const parent = node.parent;
|
|
71
160
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
161
|
+
const isTopLevel
|
|
162
|
+
= parent.type === 'Program'
|
|
163
|
+
|| parent.type === 'ExportNamedDeclaration'
|
|
164
|
+
|| parent.type === 'ExportDefaultDeclaration';
|
|
165
|
+
|
|
166
|
+
if (!isTopLevel) {
|
|
167
|
+
return;
|
|
75
168
|
}
|
|
169
|
+
|
|
170
|
+
const isExport
|
|
171
|
+
= parent.type === 'ExportNamedDeclaration'
|
|
172
|
+
|| parent.type === 'ExportDefaultDeclaration';
|
|
173
|
+
|
|
174
|
+
context.report({
|
|
175
|
+
node,
|
|
176
|
+
message: 'Top-level anonymous function declarations must be named.',
|
|
177
|
+
fix(fixer) {
|
|
178
|
+
const newName = 'defaultFunction';
|
|
179
|
+
const replacement = buildAnonymousFunctionDeclarationReplacement(
|
|
180
|
+
sourceCode,
|
|
181
|
+
node,
|
|
182
|
+
newName,
|
|
183
|
+
isExport,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return fixer.replaceText(
|
|
187
|
+
isExport ? parent : node,
|
|
188
|
+
replacement,
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
});
|
|
76
192
|
},
|
|
77
193
|
};
|
|
78
194
|
}
|