1
+ /* @internal */
2
+ namespace ts . codefix {
3
+ registerCodeFix ( {
4
+ errorCodes : [ Diagnostics . Convert_function_0_to_ES6_class . code ] ,
5
+ getCodeActions,
6
+ createCodeFixDiagnosticIfApplicable
7
+ } ) ;
8
+
9
+ function createCodeFixDiagnosticIfApplicable ( node : Node , context : CodeFixDiagnoseContext ) : Diagnostic | undefined {
10
+ if ( ! isSourceFileJavaScript ( context . boundSourceFile ) ) {
11
+ return undefined ;
12
+ }
13
+
14
+ const checker = context . program . getTypeChecker ( ) ;
15
+ const symbol = checker . getSymbolAtLocation ( node ) ;
16
+ if ( isClassLikeSymbol ( symbol ) ) {
17
+ return createDiagnosticForNode ( node , Diagnostics . Convert_function_0_to_ES6_class , symbol . name ) ;
18
+ }
19
+
20
+ function isClassLikeSymbol ( symbol : Symbol ) {
21
+ if ( ! symbol || ! symbol . valueDeclaration ) {
22
+ return false ;
23
+ }
24
+
25
+ let targetSymbol : Symbol ;
26
+ if ( symbol . valueDeclaration . kind === SyntaxKind . FunctionDeclaration ) {
27
+ targetSymbol = symbol ;
28
+ }
29
+ else if ( isDeclarationOfFunctionOrClassExpression ( symbol ) ) {
30
+ targetSymbol = ( symbol . valueDeclaration as VariableDeclaration ) . initializer . symbol ;
31
+ }
32
+
33
+ // if there is a prototype property assignment like:
34
+ // foo.prototype.method = function () { }
35
+ // then the symbol for "foo" will have a member
36
+ return targetSymbol && targetSymbol . members && targetSymbol . members . size > 0 ;
37
+ }
38
+ }
39
+
40
+ function getCodeActions ( context : CodeFixContext ) : CodeAction [ ] {
41
+ const sourceFile = context . sourceFile ;
42
+ const checker = context . program . getTypeChecker ( ) ;
43
+ const token = getTokenAtPosition ( sourceFile , context . span . start ) ;
44
+ const ctorSymbol = checker . getSymbolAtLocation ( token ) ;
45
+
46
+ const deletes : ( ( ) => any ) [ ] = [ ] ;
47
+
48
+ if ( ! ( ctorSymbol . flags & ( SymbolFlags . Function | SymbolFlags . Variable ) ) ) {
49
+ return [ ] ;
50
+ }
51
+
52
+ const ctorDeclaration = ctorSymbol . valueDeclaration ;
53
+ const changeTracker = textChanges . ChangeTracker . fromCodeFixContext ( context ) ;
54
+
55
+ let precedingNode : Node ;
56
+ let newClassDeclaration : ClassDeclaration ;
57
+ switch ( ctorDeclaration . kind ) {
58
+ case SyntaxKind . FunctionDeclaration :
59
+ precedingNode = ctorDeclaration ;
60
+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , ctorDeclaration ) ) ;
61
+ newClassDeclaration = createClassFromFunctionDeclaration ( ctorDeclaration as FunctionDeclaration ) ;
62
+ break ;
63
+
64
+ case SyntaxKind . VariableDeclaration :
65
+ precedingNode = ctorDeclaration . parent . parent ;
66
+ if ( ( < VariableDeclarationList > ctorDeclaration . parent ) . declarations . length === 1 ) {
67
+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , precedingNode ) ) ;
68
+ }
69
+ else {
70
+ deletes . push ( ( ) => changeTracker . deleteNodeInList ( sourceFile , ctorDeclaration ) ) ;
71
+ }
72
+ newClassDeclaration = createClassFromVariableDeclaration ( ctorDeclaration as VariableDeclaration ) ;
73
+ break ;
74
+ }
75
+
76
+ if ( ! newClassDeclaration ) {
77
+ return [ ] ;
78
+ }
79
+
80
+ // Because the preceding node could be touched, we need to insert nodes before delete nodes.
81
+ changeTracker . insertNodeAfter ( sourceFile , precedingNode , newClassDeclaration , { suffix : "\n" } ) ;
82
+ for ( const deleteCallback of deletes ) {
83
+ deleteCallback ( ) ;
84
+ }
85
+
86
+ return [ {
87
+ description : `Convert function ${ ctorSymbol . name } to ES6 class` ,
88
+ changes : changeTracker . getChanges ( )
89
+ } ] ;
90
+
91
+ function createClassElementsFromSymbol ( symbol : Symbol ) {
92
+ const memberElements : ClassElement [ ] = [ ] ;
93
+ // all instance members are stored in the "member" array of symbol
94
+ if ( symbol . members ) {
95
+ symbol . members . forEach ( member => {
96
+ const memberElement = createClassElement ( member , /*modifiers*/ undefined ) ;
97
+ if ( memberElement ) {
98
+ memberElements . push ( memberElement ) ;
99
+ }
100
+ } ) ;
101
+ }
102
+
103
+ // all static members are stored in the "exports" array of symbol
104
+ if ( symbol . exports ) {
105
+ symbol . exports . forEach ( member => {
106
+ const memberElement = createClassElement ( member , [ createToken ( SyntaxKind . StaticKeyword ) ] ) ;
107
+ if ( memberElement ) {
108
+ memberElements . push ( memberElement ) ;
109
+ }
110
+ } ) ;
111
+ }
112
+
113
+ return memberElements ;
114
+
115
+ function createClassElement ( symbol : Symbol , modifiers : Modifier [ ] ) : ClassElement {
116
+ // both properties and methods are bound as property symbols
117
+ if ( ! ( symbol . flags & SymbolFlags . Property ) ) {
118
+ return ;
119
+ }
120
+
121
+ const memberDeclaration = symbol . valueDeclaration as PropertyAccessExpression ;
122
+ const assignmentBinaryExpression = memberDeclaration . parent as BinaryExpression ;
123
+
124
+ // delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
125
+ const nodeToDelete = assignmentBinaryExpression . parent && assignmentBinaryExpression . parent . kind === SyntaxKind . ExpressionStatement
126
+ ? assignmentBinaryExpression . parent : assignmentBinaryExpression ;
127
+ deletes . push ( ( ) => changeTracker . deleteNode ( sourceFile , nodeToDelete ) ) ;
128
+
129
+ if ( ! assignmentBinaryExpression . right ) {
130
+ return createProperty ( [ ] , modifiers , symbol . name , /*questionToken*/ undefined ,
131
+ /*type*/ undefined , /*initializer*/ undefined ) ;
132
+ }
133
+
134
+ switch ( assignmentBinaryExpression . right . kind ) {
135
+ case SyntaxKind . FunctionExpression :
136
+ const functionExpression = assignmentBinaryExpression . right as FunctionExpression ;
137
+ return createMethodDeclaration ( /*decorators*/ undefined , modifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
138
+ /*typeParameters*/ undefined , functionExpression . parameters , /*type*/ undefined , functionExpression . body ) ;
139
+
140
+ case SyntaxKind . ArrowFunction :
141
+ const arrowFunction = assignmentBinaryExpression . right as ArrowFunction ;
142
+ const arrowFunctionBody = arrowFunction . body ;
143
+ let bodyBlock : Block ;
144
+
145
+ // case 1: () => { return [1,2,3] }
146
+ if ( arrowFunctionBody . kind === SyntaxKind . Block ) {
147
+ bodyBlock = arrowFunctionBody as Block ;
148
+ }
149
+ // case 2: () => [1,2,3]
150
+ else {
151
+ const expression = arrowFunctionBody as Expression ;
152
+ bodyBlock = createBlock ( [ createReturn ( expression ) ] ) ;
153
+ }
154
+ return createMethodDeclaration ( /*decorators*/ undefined , modifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
155
+ /*typeParameters*/ undefined , arrowFunction . parameters , /*type*/ undefined , bodyBlock ) ;
156
+ default :
157
+ return createProperty ( /*decorators*/ undefined , modifiers , memberDeclaration . name , /*questionToken*/ undefined ,
158
+ /*type*/ undefined , assignmentBinaryExpression . right ) ;
159
+ }
160
+ }
161
+ }
162
+
163
+ function createClassFromVariableDeclaration ( node : VariableDeclaration ) : ClassDeclaration {
164
+ const initializer = node . initializer as FunctionExpression ;
165
+ if ( ! initializer || initializer . kind !== SyntaxKind . FunctionExpression ) {
166
+ return undefined ;
167
+ }
168
+
169
+ if ( node . name . kind !== SyntaxKind . Identifier ) {
170
+ return undefined ;
171
+ }
172
+
173
+ const memberElements = createClassElementsFromSymbol ( initializer . symbol ) ;
174
+ if ( initializer . body ) {
175
+ memberElements . unshift ( createConstructor ( /*decorators*/ undefined , /*modifiers*/ undefined , initializer . parameters , initializer . body ) ) ;
176
+ }
177
+
178
+ return createClassDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined , node . name ,
179
+ /*typeParameters*/ undefined , /*heritageClauses*/ undefined , memberElements ) ;
180
+ }
181
+
182
+ function createClassFromFunctionDeclaration ( node : FunctionDeclaration ) : ClassDeclaration {
183
+ const memberElements = createClassElementsFromSymbol ( ctorSymbol ) ;
184
+ if ( node . body ) {
185
+ memberElements . unshift ( createConstructor ( /*decorators*/ undefined , /*modifiers*/ undefined , node . parameters , node . body ) ) ;
186
+ }
187
+ return createClassDeclaration ( /*decorators*/ undefined , /*modifiers*/ undefined , node . name ,
188
+ /*typeParameters*/ undefined , /*heritageClauses*/ undefined , memberElements ) ;
189
+ }
190
+ }
191
+ }
0 commit comments