11/* eslint-disable unicorn/prefer-module */
22
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- */
143const meta = {
154 type : 'suggestion' ,
165 docs : {
@@ -29,10 +18,13 @@ const meta = {
2918 * @param {string } funcName - The name of the new function.
3019 * @param {ArrowFunctionExpression } arrowNode - The ArrowFunctionExpression node.
3120 * @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 = ...`) .
3322 * @returns {string } The replacement code.
3423 */
3524function buildArrowFunctionReplacement ( functionName , arrowNode , sourceCode , isExport ) {
25+ const asyncKeyword = arrowNode . async ? 'async ' : '' ;
26+ const exportKeyword = isExport ? 'export ' : '' ;
27+
3628 const parametersText = arrowNode . params . map ( parameter => sourceCode . getText ( parameter ) ) . join ( ', ' ) ;
3729
3830 let bodyText ;
@@ -43,8 +35,7 @@ function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isEx
4335 bodyText = `{ return ${ expressionText } ; }` ;
4436 }
4537
46- const exportKeyword = isExport ? 'export ' : '' ;
47- return `${ exportKeyword } function ${ functionName } (${ parametersText } ) ${ bodyText } ` ;
38+ return `${ exportKeyword } ${ asyncKeyword } function ${ functionName } (${ parametersText } ) ${ bodyText } ` ;
4839}
4940
5041/**
@@ -57,36 +48,41 @@ function buildArrowFunctionReplacement(functionName, arrowNode, sourceCode, isEx
5748 * @returns {string } The replacement code.
5849 */
5950function buildFunctionExpressionReplacement ( functionName , functionExprNode , sourceCode , isExport ) {
51+ const asyncKeyword = functionExprNode . async ? 'async ' : '' ;
52+ const exportKeyword = isExport ? 'export ' : '' ;
53+
6054 const parametersText = functionExprNode . params . map ( parameter => sourceCode . getText ( parameter ) ) . join ( ', ' ) ;
6155 const bodyText = sourceCode . getText ( functionExprNode . body ) ;
6256
63- const exportKeyword = isExport ? 'export ' : '' ;
64- return `${ exportKeyword } function ${ functionName } (${ parametersText } ) ${ bodyText } ` ;
57+ return `${ exportKeyword } ${ asyncKeyword } function ${ functionName } (${ parametersText } ) ${ bodyText } ` ;
6558}
6659
6760/**
68- * Build a replacement for an anonymous top-level FunctionDeclaration.
61+ * Build a replacement for an anonymous top-level FunctionDeclaration (including async) .
6962 *
7063 * @param {import('eslint').SourceCode } sourceCode
7164 * @param {import('estree').FunctionDeclaration } node
7265 * @param {string } [funcName='defaultFunction']
66+ * @param {boolean } [isExport=false]
7367 */
74- function buildAnonymousFunctionDeclarationReplacement ( sourceCode , node , functionName = 'defaultFunction' ) {
68+ function buildAnonymousFunctionDeclarationReplacement ( sourceCode , node , functionName = 'defaultFunction' , isExport = false ) {
7569 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 * a s y n c \s + f u n c t i o n \s * \( / ;
75+ const functionRegex = / ^ \s * f u n c t i o n \s * \( / ;
76+
77+ replaced = asyncFunctionRegex . test ( replaced ) ? replaced . replace ( asyncFunctionRegex , `async function ${ functionName } (` ) : replaced . replace ( functionRegex , `function ${ functionName } (` ) ;
7678
77- const fixedText = originalText . replace (
78- / ^ ( \s * f u n c t i o n \s * ) \( / ,
79- `$1 ${ functionName } (` ,
80- ) ;
81- return fixedText ;
79+ if ( isExport && ! replaced . trimStart ( ) . startsWith ( 'export' ) ) {
80+ replaced = ` ${ exportKeyword } ${ replaced } ` ;
81+ }
82+
83+ return replaced ;
8284}
8385
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- */
9086function create ( context ) {
9187 const sourceCode = context . getSourceCode ( ) ;
9288
@@ -129,7 +125,10 @@ function create(context) {
129125 isExport ,
130126 ) ;
131127
132- return fixer . replaceText ( grandParent . type === 'Program' ? declParent : grandParent , replacement ) ;
128+ return fixer . replaceText (
129+ isExport ? grandParent : declParent ,
130+ replacement ,
131+ ) ;
133132 } ,
134133 } ) ;
135134 } else if ( node . init . type === 'FunctionExpression' ) {
@@ -143,7 +142,10 @@ function create(context) {
143142 sourceCode ,
144143 isExport ,
145144 ) ;
146- return fixer . replaceText ( grandParent . type === 'Program' ? declParent : grandParent , replacement ) ;
145+ return fixer . replaceText (
146+ isExport ? grandParent : declParent ,
147+ replacement ,
148+ ) ;
147149 } ,
148150 } ) ;
149151 }
@@ -156,23 +158,33 @@ function create(context) {
156158
157159 const parent = node . parent ;
158160
159- const isTopLevel = parent . type === 'Program'
161+ const isTopLevel
162+ = parent . type === 'Program'
160163 || parent . type === 'ExportNamedDeclaration'
161164 || parent . type === 'ExportDefaultDeclaration' ;
162165
163166 if ( ! isTopLevel ) {
164167 return ;
165168 }
166169
170+ const isExport
171+ = parent . type === 'ExportNamedDeclaration'
172+ || parent . type === 'ExportDefaultDeclaration' ;
173+
167174 context . report ( {
168175 node,
169176 message : 'Top-level anonymous function declarations must be named.' ,
170177 fix ( fixer ) {
171178 const newName = 'defaultFunction' ;
172- const replacement = buildAnonymousFunctionDeclarationReplacement ( sourceCode , node , newName ) ;
179+ const replacement = buildAnonymousFunctionDeclarationReplacement (
180+ sourceCode ,
181+ node ,
182+ newName ,
183+ isExport ,
184+ ) ;
173185
174186 return fixer . replaceText (
175- parent . type === 'Program' ? node : parent ,
187+ isExport ? parent : node ,
176188 replacement ,
177189 ) ;
178190 } ,
0 commit comments