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