@@ -17,9 +17,32 @@ export class Writer {
1717
1818 getBodySource ( { body } : AnyFunction ) : string {
1919 if ( this . options . returnStyle !== 'explicit' && this . guard . isBlockStatementWithSingleReturn ( body ) ) {
20- const returnValue = body . body [ 0 ] . argument ;
21- const source = this . sourceCode . getText ( returnValue ) ;
22- return returnValue . type === AST_NODE_TYPES . ObjectExpression ? `(${ source } )` : source ;
20+ const firstStatementInFunction = body . body [ 0 ] ;
21+ const firstStatementValue = firstStatementInFunction . argument ;
22+
23+ // Get a comment after an opening brace and before a first statement in a function
24+ const commentBeforeFirstStatement = this . sourceCode . getCommentsBefore ( firstStatementInFunction ) [ 0 ] ;
25+
26+ const bodySource = this . sourceCode . getText ( firstStatementValue ) ;
27+ const wrappedBodySource =
28+ firstStatementValue . type === AST_NODE_TYPES . ObjectExpression ? `(${ bodySource } )` : bodySource ;
29+
30+ // Get a text of a comment after an opening brace in a function if this comment exists
31+ if ( ! commentBeforeFirstStatement ) {
32+ return wrappedBodySource ;
33+ }
34+ const commentBeforeFirstStatementText = this . sourceCode . getText ( commentBeforeFirstStatement ) ;
35+
36+ /* Return a function body with a comment before it.
37+
38+ This method adds line breaks before and after a comment. Reasons:
39+
40+ 1. If a comment is single-line, a new line after this comment is required, because
41+ a comment in the same line as a first statement of a function is incorrect syntax.
42+
43+ 2. A new line before a comment added, because otherwise JavaScript beautifiers
44+ may prettify a comment not in the best way. */
45+ return `\n${ commentBeforeFirstStatementText } \n${ wrappedBodySource } ` ;
2346 }
2447 if ( this . guard . hasImplicitReturn ( body ) && this . options . returnStyle !== 'implicit' ) {
2548 return `{ return ${ this . sourceCode . getText ( body ) } }` ;
@@ -29,19 +52,19 @@ export class Writer {
2952
3053 getParamsSource ( params : TSESTree . Parameter [ ] ) : string [ ] {
3154 return params . map ( ( param ) => {
32- // Get parameter value
33- const paramText = this . sourceCode . getText ( param ) ;
55+ // Get a parameter value
56+ const parameterText = this . sourceCode . getText ( param ) ;
3457
35- // Get a comment before parameter if exists
36- const commentsBefore = this . sourceCode . getCommentsBefore ( param ) ;
37- const beforeText = commentsBefore . length > 0 ? this . sourceCode . getText ( commentsBefore [ 0 ] ) : '' ;
58+ // Get a comment before a parameter if exists
59+ const commentBeforeParameter = this . sourceCode . getCommentsBefore ( param ) [ 0 ] ;
60+ const commentBeforeParameterText = commentBeforeParameter ? this . sourceCode . getText ( commentBeforeParameter ) : '' ;
3861
39- // Get a comment after parameter if exists
40- const commentsAfter = this . sourceCode . getCommentsAfter ( param ) ;
41- const afterText = commentsAfter . length > 0 ? this . sourceCode . getText ( commentsAfter [ 0 ] ) : '' ;
62+ // Get a comment after a parameter if exists
63+ const commentAfterParameter = this . sourceCode . getCommentsAfter ( param ) [ 0 ] ;
64+ const commentAfterParameterText = commentAfterParameter ? this . sourceCode . getText ( commentAfterParameter ) : '' ;
4265
43- // Combine all parts
44- return beforeText + paramText + afterText ;
66+ // Return a parameter with comments before and after it
67+ return ` ${ commentBeforeParameterText } ${ parameterText } ${ commentAfterParameterText } ` ;
4568 } ) ;
4669 }
4770
@@ -70,34 +93,67 @@ export class Writer {
7093 const RETURN_TYPE = fn . returnType ? fn . returnType : '' ;
7194 const PARAMS = fn . params . join ( ', ' ) ;
7295
73- // Preserve a comment after a return type annotation
74- const commentInside = this . sourceCode . getCommentsInside ( node ) ;
75- let middleComment = '' ;
96+ /* Get all comments of a function:
7697
77- if ( commentInside . length > 0 ) {
78- // Find a comment between a return type and a function body
79- const returnTypeComment = commentInside . find ( ( candidateComment ) => {
80- // Get the start position of a candidate comment
81- const commentStart = candidateComment . range [ 0 ] ;
98+ 1. Comments after parameters
99+ 2. Comments between parameters and a function body
100+ 3. Comments inside a function body */
101+ const allFunctionComments = this . sourceCode . getCommentsInside ( node ) ;
82102
83- /* If a return type exists, use its end position.
84- Otherwise, use the end of the last function parameter */
85- const returnTypeEnd = node . returnType ? node . returnType . range [ 1 ] : node . params [ node . params . length - 1 ] . range [ 1 ] ;
103+ let preservedCommentText = '' ;
86104
87- // Get the start position of a function body
88- const bodyStart = node . body . range [ 0 ] ;
105+ // Checking if comments exists instead of using the “find()” method everywhere for improving performance
106+ if ( allFunctionComments [ 0 ] ) {
107+ /* Find a comment between parameters and a function body.
89108
90- // Keep a comment between a return type and a function body
91- return commentStart > returnTypeEnd && commentStart < bodyStart ;
109+ It’s possible to get the value of the “commentBetweenParametersAndFunctionBodyText”
110+ use more simple way, but if a JavaScript function contain not solely a comment
111+ between parameters and a function body, eslint-plugin-prefer-arrow-functions may work incorrectly.
112+ Therefore, I use the method “getCommentsInside(node)”. */
113+ const commentBetweenParametersAndFunctionBody = allFunctionComments . find ( ( candidateComment ) => {
114+ // Get the start position of a candidate comment
115+ const candidateCommentStartPosition = candidateComment . range [ 0 ] ;
116+
117+ // Get the position of the closing parenthesis after parameters if parameters exist or no
118+ const parametersClosingParenthesisPosition = node . params [ 0 ]
119+ ? /* Use the old “length()” property instead of the modern method “at()”,
120+ because in TypeScript “at()” usage isn’t simple:
121+ https://github.com/microsoft/TypeScript/issues/57224 */
122+ node . params [ node . params . length - 1 ] . range [ 1 ] + 1
123+ : node . range [ 0 ] + 1 ;
124+
125+ // Get the position of the opening brace of a function body
126+ const functionBodyOpeningBracePosition = node . body . range [ 0 ] ;
127+
128+ // Keep a comment between parameters and a function body if exists
129+ return (
130+ candidateCommentStartPosition > parametersClosingParenthesisPosition &&
131+ candidateCommentStartPosition < functionBodyOpeningBracePosition
132+ ) ;
92133 } ) ;
93134
94- // If a return type comment exists, get its text
95- if ( returnTypeComment ) {
96- middleComment = this . sourceCode . getText ( returnTypeComment ) ;
135+ /* If a comment between parameters and a function body exists,
136+ get its text and convert it to a single-line comment if it’s a multiline */
137+ if ( commentBetweenParametersAndFunctionBody ) {
138+ let commentBetweenParametersAndFunctionBodyText = this . sourceCode . getText (
139+ commentBetweenParametersAndFunctionBody ,
140+ ) ;
141+
142+ // Check if a comment is multiline
143+ if ( commentBetweenParametersAndFunctionBodyText . includes ( '\n' ) ) {
144+ /* Convert a multiline comment with line breaks to a single-line (replace line breaks with spaces),
145+ because JavaScript doesn’t support a multiline comment before a fat arrow. */
146+ commentBetweenParametersAndFunctionBodyText = commentBetweenParametersAndFunctionBodyText
147+ . split ( / \r ? \n / )
148+ . map ( ( commentLine ) => commentLine . trim ( ) )
149+ . join ( ' ' ) ;
150+ }
151+
152+ preservedCommentText = commentBetweenParametersAndFunctionBodyText ;
97153 }
98154 }
99155
100- const arrowFunction = `${ ASYNC } ${ GENERIC } (${ PARAMS } )${ RETURN_TYPE } ${ middleComment } => ${ BODY } ` ;
156+ const arrowFunction = `${ ASYNC } ${ GENERIC } (${ PARAMS } )${ preservedCommentText } ${ RETURN_TYPE } => ${ BODY } ` ;
101157
102158 // Check if parentheses are needed due to operator precedence
103159 if ( this . needsParentheses ( node ) ) {
0 commit comments