eslint-plugin-th-rules 1.15.5 → 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 CHANGED
@@ -1,3 +1,10 @@
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
+
1
8
  ## [1.15.5](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.4...v1.15.5) (2024-12-30)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-th-rules",
3
- "version": "1.15.5",
3
+ "version": "1.15.6",
4
4
  "description": "A List of custom ESLint rules created by Tomer Horowitz",
5
5
  "keywords": [
6
6
  "eslint",
@@ -1,16 +1,5 @@
1
1
  /* eslint-disable unicorn/prefer-module */
2
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
- */
14
3
  const meta = {
15
4
  type: 'suggestion',
16
5
  docs: {
@@ -29,10 +18,13 @@ const meta = {
29
18
  * @param {string} funcName - The name of the new function.
30
19
  * @param {ArrowFunctionExpression} arrowNode - The ArrowFunctionExpression node.
31
20
  * @param {import('eslint').SourceCode} sourceCode - The ESLint SourceCode object.
32
- * @param {boolean} isExport - Whether or not this function is exported.
21
+ * @param {boolean} isExport - Whether or not this function is exported (e.g., `export const foo = ...`).
33
22
  * @returns {string} The replacement code.
34
23
  */
35
24
  function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isExport) {
25
+ const asyncKeyword = arrowNode.async ? 'async ' : '';
26
+ const exportKeyword = isExport ? 'export ' : '';
27
+
36
28
  const parametersText = arrowNode.params.map(parameter => sourceCode.getText(parameter)).join(', ');
37
29
 
38
30
  let bodyText;
@@ -43,8 +35,7 @@ function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isEx
43
35
  bodyText = `{ return ${expressionText}; }`;
44
36
  }
45
37
 
46
- const exportKeyword = isExport ? 'export ' : '';
47
- return `${exportKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
38
+ return `${exportKeyword}${asyncKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
48
39
  }
49
40
 
50
41
  /**
@@ -57,36 +48,41 @@ function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isEx
57
48
  * @returns {string} The replacement code.
58
49
  */
59
50
  function buildFunctionExpressionReplacement(functionName, functionExprNode, sourceCode, isExport) {
51
+ const asyncKeyword = functionExprNode.async ? 'async ' : '';
52
+ const exportKeyword = isExport ? 'export ' : '';
53
+
60
54
  const parametersText = functionExprNode.params.map(parameter => sourceCode.getText(parameter)).join(', ');
61
55
  const bodyText = sourceCode.getText(functionExprNode.body);
62
56
 
63
- const exportKeyword = isExport ? 'export ' : '';
64
- return `${exportKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
57
+ return `${exportKeyword}${asyncKeyword}function ${functionName}(${parametersText}) ${bodyText}`;
65
58
  }
66
59
 
67
60
  /**
68
- * Build a replacement for an anonymous top-level FunctionDeclaration.
61
+ * Build a replacement for an anonymous top-level FunctionDeclaration (including async).
69
62
  *
70
63
  * @param {import('eslint').SourceCode} sourceCode
71
64
  * @param {import('estree').FunctionDeclaration} node
72
65
  * @param {string} [funcName='defaultFunction']
66
+ * @param {boolean} [isExport=false]
73
67
  */
74
- function buildAnonymousFunctionDeclarationReplacement(sourceCode, node, functionName = 'defaultFunction') {
68
+ function buildAnonymousFunctionDeclarationReplacement(sourceCode, node, functionName = 'defaultFunction', isExport = false) {
75
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}(`);
76
78
 
77
- const fixedText = originalText.replace(
78
- /^(\s*function\s*)\(/,
79
- `$1${functionName}(`,
80
- );
81
- return fixedText;
79
+ if (isExport && !replaced.trimStart().startsWith('export')) {
80
+ replaced = `${exportKeyword}${replaced}`;
81
+ }
82
+
83
+ return replaced;
82
84
  }
83
85
 
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
- */
90
86
  function create(context) {
91
87
  const sourceCode = context.getSourceCode();
92
88
 
@@ -129,7 +125,10 @@ function create(context) {
129
125
  isExport,
130
126
  );
131
127
 
132
- return fixer.replaceText(grandParent.type === 'Program' ? declParent : grandParent, replacement);
128
+ return fixer.replaceText(
129
+ isExport ? grandParent : declParent,
130
+ replacement,
131
+ );
133
132
  },
134
133
  });
135
134
  } else if (node.init.type === 'FunctionExpression') {
@@ -143,7 +142,10 @@ function create(context) {
143
142
  sourceCode,
144
143
  isExport,
145
144
  );
146
- return fixer.replaceText(grandParent.type === 'Program' ? declParent : grandParent, replacement);
145
+ return fixer.replaceText(
146
+ isExport ? grandParent : declParent,
147
+ replacement,
148
+ );
147
149
  },
148
150
  });
149
151
  }
@@ -156,7 +158,8 @@ function create(context) {
156
158
 
157
159
  const parent = node.parent;
158
160
 
159
- const isTopLevel = parent.type === 'Program'
161
+ const isTopLevel
162
+ = parent.type === 'Program'
160
163
  || parent.type === 'ExportNamedDeclaration'
161
164
  || parent.type === 'ExportDefaultDeclaration';
162
165
 
@@ -164,15 +167,24 @@ function create(context) {
164
167
  return;
165
168
  }
166
169
 
170
+ const isExport
171
+ = parent.type === 'ExportNamedDeclaration'
172
+ || parent.type === 'ExportDefaultDeclaration';
173
+
167
174
  context.report({
168
175
  node,
169
176
  message: 'Top-level anonymous function declarations must be named.',
170
177
  fix(fixer) {
171
178
  const newName = 'defaultFunction';
172
- const replacement = buildAnonymousFunctionDeclarationReplacement(sourceCode, node, newName);
179
+ const replacement = buildAnonymousFunctionDeclarationReplacement(
180
+ sourceCode,
181
+ node,
182
+ newName,
183
+ isExport,
184
+ );
173
185
 
174
186
  return fixer.replaceText(
175
- parent.type === 'Program' ? node : parent,
187
+ isExport ? parent : node,
176
188
  replacement,
177
189
  );
178
190
  },