|
1 | 1 | import {
|
2 | 2 | AST_NODE_TYPES,
|
3 | 3 | AST_TOKEN_TYPES,
|
| 4 | + TSESLint, |
4 | 5 | TSESTree,
|
5 | 6 | } from '@typescript-eslint/experimental-utils';
|
6 | 7 | import * as util from '../util';
|
@@ -69,46 +70,6 @@ export default util.createRule({
|
69 | 70 | }
|
70 | 71 | }
|
71 | 72 |
|
72 |
| - /** |
73 |
| - * @param call The call signature causing the diagnostic |
74 |
| - * @param parent The parent of the call |
75 |
| - * @returns The suggestion to report |
76 |
| - */ |
77 |
| - function renderSuggestion( |
78 |
| - call: |
79 |
| - | TSESTree.TSCallSignatureDeclaration |
80 |
| - | TSESTree.TSConstructSignatureDeclaration, |
81 |
| - parent: TSESTree.Node, |
82 |
| - ): string { |
83 |
| - const start = call.range[0]; |
84 |
| - const colonPos = call.returnType!.range[0] - start; |
85 |
| - const text = sourceCode.getText().slice(start, call.range[1]); |
86 |
| - |
87 |
| - let suggestion = `${text.slice(0, colonPos)} =>${text.slice( |
88 |
| - colonPos + 1, |
89 |
| - )}`; |
90 |
| - |
91 |
| - const lastChar = suggestion.endsWith(';') ? ';' : ''; |
92 |
| - if (lastChar) { |
93 |
| - suggestion = suggestion.slice(0, -1); |
94 |
| - } |
95 |
| - if (shouldWrapSuggestion(parent.parent)) { |
96 |
| - suggestion = `(${suggestion})`; |
97 |
| - } |
98 |
| - if (parent.type === AST_NODE_TYPES.TSInterfaceDeclaration) { |
99 |
| - if (typeof parent.typeParameters !== 'undefined') { |
100 |
| - return `type ${sourceCode |
101 |
| - .getText() |
102 |
| - .slice( |
103 |
| - parent.id.range[0], |
104 |
| - parent.typeParameters.range[1], |
105 |
| - )} = ${suggestion}${lastChar}`; |
106 |
| - } |
107 |
| - return `type ${parent.id.name} = ${suggestion}${lastChar}`; |
108 |
| - } |
109 |
| - return suggestion; |
110 |
| - } |
111 |
| - |
112 | 73 | /**
|
113 | 74 | * @param member The TypeElement being checked
|
114 | 75 | * @param node The parent of member being checked
|
@@ -140,30 +101,97 @@ export default util.createRule({
|
140 | 101 | });
|
141 | 102 | return;
|
142 | 103 | }
|
143 |
| - const suggestion = renderSuggestion(member, node); |
144 |
| - const fixStart = |
145 |
| - node.type === AST_NODE_TYPES.TSTypeLiteral |
146 |
| - ? node.range[0] |
147 |
| - : sourceCode |
148 |
| - .getTokens(node) |
149 |
| - .filter( |
150 |
| - token => |
151 |
| - token.type === AST_TOKEN_TYPES.Keyword && |
152 |
| - token.value === 'interface', |
153 |
| - )[0].range[0]; |
154 | 104 |
|
| 105 | + const fixable = |
| 106 | + node.parent && |
| 107 | + node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration; |
155 | 108 | context.report({
|
156 | 109 | node: member,
|
157 | 110 | messageId: 'functionTypeOverCallableType',
|
158 | 111 | data: {
|
159 | 112 | literalOrInterface: phrases[node.type],
|
160 | 113 | },
|
161 |
| - fix(fixer) { |
162 |
| - return fixer.replaceTextRange( |
163 |
| - [fixStart, node.range[1]], |
164 |
| - suggestion, |
165 |
| - ); |
166 |
| - }, |
| 114 | + fix: fixable |
| 115 | + ? null |
| 116 | + : (fixer): TSESLint.RuleFix[] => { |
| 117 | + const fixes: TSESLint.RuleFix[] = []; |
| 118 | + const start = member.range[0]; |
| 119 | + const colonPos = member.returnType!.range[0] - start; |
| 120 | + const text = sourceCode.getText().slice(start, member.range[1]); |
| 121 | + const comments = sourceCode |
| 122 | + .getCommentsBefore(member) |
| 123 | + .concat(sourceCode.getCommentsAfter(member)); |
| 124 | + let suggestion = `${text.slice(0, colonPos)} =>${text.slice( |
| 125 | + colonPos + 1, |
| 126 | + )}`; |
| 127 | + const lastChar = suggestion.endsWith(';') ? ';' : ''; |
| 128 | + if (lastChar) { |
| 129 | + suggestion = suggestion.slice(0, -1); |
| 130 | + } |
| 131 | + if (shouldWrapSuggestion(node.parent)) { |
| 132 | + suggestion = `(${suggestion})`; |
| 133 | + } |
| 134 | + |
| 135 | + if (node.type === AST_NODE_TYPES.TSInterfaceDeclaration) { |
| 136 | + if (typeof node.typeParameters !== 'undefined') { |
| 137 | + suggestion = `type ${sourceCode |
| 138 | + .getText() |
| 139 | + .slice( |
| 140 | + node.id.range[0], |
| 141 | + node.typeParameters.range[1], |
| 142 | + )} = ${suggestion}${lastChar}`; |
| 143 | + } else { |
| 144 | + suggestion = `type ${node.id.name} = ${suggestion}${lastChar}`; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + const isParentExported = |
| 149 | + node.parent && |
| 150 | + node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration; |
| 151 | + |
| 152 | + if ( |
| 153 | + node.type === AST_NODE_TYPES.TSInterfaceDeclaration && |
| 154 | + isParentExported |
| 155 | + ) { |
| 156 | + const commentsText = comments.reduce((text, comment) => { |
| 157 | + return ( |
| 158 | + text + |
| 159 | + (comment.type === AST_TOKEN_TYPES.Line |
| 160 | + ? `//${comment.value}` |
| 161 | + : `/*${comment.value}*/`) + |
| 162 | + '\n' |
| 163 | + ); |
| 164 | + }, ''); |
| 165 | + // comments should move before export and not between export and interface declaration |
| 166 | + fixes.push( |
| 167 | + fixer.insertTextBefore( |
| 168 | + node.parent as TSESTree.Node | TSESTree.Token, |
| 169 | + commentsText, |
| 170 | + ), |
| 171 | + ); |
| 172 | + } else { |
| 173 | + comments.forEach(comment => { |
| 174 | + let commentText = |
| 175 | + comment.type === AST_TOKEN_TYPES.Line |
| 176 | + ? `//${comment.value}` |
| 177 | + : `/*${comment.value}*/`; |
| 178 | + const isCommentOnTheSameLine = |
| 179 | + comment.loc.start.line === member.loc.start.line; |
| 180 | + if (!isCommentOnTheSameLine) { |
| 181 | + commentText += '\n'; |
| 182 | + } else { |
| 183 | + commentText += ' '; |
| 184 | + } |
| 185 | + suggestion = commentText + suggestion; |
| 186 | + }); |
| 187 | + } |
| 188 | + |
| 189 | + const fixStart = node.range[0]; |
| 190 | + fixes.push( |
| 191 | + fixer.replaceTextRange([fixStart, node.range[1]], suggestion), |
| 192 | + ); |
| 193 | + return fixes; |
| 194 | + }, |
167 | 195 | });
|
168 | 196 | }
|
169 | 197 | }
|
|
0 commit comments