@@ -60,95 +60,161 @@ namespace ts.codefix {
60
60
const memberElements : ClassElement [ ] = [ ] ;
61
61
// all instance members are stored in the "member" array of symbol
62
62
if ( symbol . members ) {
63
- symbol . members . forEach ( member => {
63
+ symbol . members . forEach ( ( member , key ) => {
64
+ if ( key === "constructor" ) {
65
+ // fn.prototype.constructor = fn
66
+ changes . delete ( sourceFile , member . valueDeclaration . parent ) ;
67
+ return ;
68
+ }
64
69
const memberElement = createClassElement ( member , /*modifiers*/ undefined ) ;
65
70
if ( memberElement ) {
66
- memberElements . push ( memberElement ) ;
71
+ memberElements . push ( ... memberElement ) ;
67
72
}
68
73
} ) ;
69
74
}
70
75
71
76
// all static members are stored in the "exports" array of symbol
72
77
if ( symbol . exports ) {
73
78
symbol . exports . forEach ( member => {
74
- const memberElement = createClassElement ( member , [ createToken ( SyntaxKind . StaticKeyword ) ] ) ;
75
- if ( memberElement ) {
76
- memberElements . push ( memberElement ) ;
79
+ if ( member . name === "prototype" ) {
80
+ const firstDeclaration = member . declarations [ 0 ] ;
81
+ // only one "x.prototype = { ... }" will pass
82
+ if ( member . declarations . length === 1 &&
83
+ isPropertyAccessExpression ( firstDeclaration ) &&
84
+ isBinaryExpression ( firstDeclaration . parent ) &&
85
+ firstDeclaration . parent . operatorToken . kind === SyntaxKind . EqualsToken &&
86
+ isObjectLiteralExpression ( firstDeclaration . parent . right )
87
+ ) {
88
+ const prototypes = firstDeclaration . parent . right ;
89
+ const memberElement = createClassElement ( prototypes . symbol , /** modifiers */ undefined ) ;
90
+ if ( memberElement ) {
91
+ memberElements . push ( ...memberElement ) ;
92
+ }
93
+ }
94
+ }
95
+ else {
96
+ const memberElement = createClassElement ( member , [ createToken ( SyntaxKind . StaticKeyword ) ] ) ;
97
+ if ( memberElement ) {
98
+ memberElements . push ( ...memberElement ) ;
99
+ }
77
100
}
78
101
} ) ;
79
102
}
80
103
81
104
return memberElements ;
82
105
83
- function shouldConvertDeclaration ( _target : PropertyAccessExpression , source : Expression ) {
84
- // Right now the only thing we can convert are function expressions - other values shouldn't get
85
- // transformed. We can update this once ES public class properties are available.
86
- return isFunctionLike ( source ) ;
106
+ function shouldConvertDeclaration ( _target : PropertyAccessExpression | ObjectLiteralExpression , source : Expression ) {
107
+ // Right now the only thing we can convert are function expressions, get/set accessors and methods
108
+ // other values like normal value fields ({a: 1}) shouldn't get transformed.
109
+ // We can update this once ES public class properties are available.
110
+ if ( isPropertyAccessExpression ( _target ) ) {
111
+ if ( isConstructorAssignment ( _target ) ) return true ;
112
+ return isFunctionLike ( source ) ;
113
+ }
114
+ else {
115
+ return every ( _target . properties , property => {
116
+ // a() {}
117
+ if ( isMethodDeclaration ( property ) || isGetOrSetAccessorDeclaration ( property ) ) return true ;
118
+ // a: function() {}
119
+ if ( isPropertyAssignment ( property ) && isFunctionExpression ( property . initializer ) && ! ! property . name ) return true ;
120
+ // x.prototype.constructor = fn
121
+ if ( isConstructorAssignment ( property ) ) return true ;
122
+ return false ;
123
+ } ) ;
124
+ }
87
125
}
88
126
89
- function createClassElement ( symbol : Symbol , modifiers : Modifier [ ] | undefined ) : ClassElement | undefined {
127
+ function createClassElement ( symbol : Symbol , modifiers : Modifier [ ] | undefined ) : readonly ClassElement [ ] {
90
128
// Right now the only thing we can convert are function expressions, which are marked as methods
91
- if ( ! ( symbol . flags & SymbolFlags . Method ) ) {
92
- return ;
129
+ // or { x: y } type prototype assignments, which are marked as ObjectLiteral
130
+ const members : ClassElement [ ] = [ ] ;
131
+ if ( ! ( symbol . flags & SymbolFlags . Method ) && ! ( symbol . flags & SymbolFlags . ObjectLiteral ) ) {
132
+ return members ;
93
133
}
94
134
95
- const memberDeclaration = symbol . valueDeclaration as PropertyAccessExpression ;
135
+ const memberDeclaration = symbol . valueDeclaration as PropertyAccessExpression | ObjectLiteralExpression ;
96
136
const assignmentBinaryExpression = memberDeclaration . parent as BinaryExpression ;
137
+ const assignmentExpr = assignmentBinaryExpression . right ;
97
138
98
- if ( ! shouldConvertDeclaration ( memberDeclaration , assignmentBinaryExpression . right ) ) {
99
- return ;
139
+ if ( ! shouldConvertDeclaration ( memberDeclaration , assignmentExpr ) ) {
140
+ return members ;
100
141
}
101
142
102
143
// delete the entire statement if this expression is the sole expression to take care of the semicolon at the end
103
144
const nodeToDelete = assignmentBinaryExpression . parent && assignmentBinaryExpression . parent . kind === SyntaxKind . ExpressionStatement
104
145
? assignmentBinaryExpression . parent : assignmentBinaryExpression ;
105
146
changes . delete ( sourceFile , nodeToDelete ) ;
106
147
107
- if ( ! assignmentBinaryExpression . right ) {
108
- return createProperty ( [ ] , modifiers , symbol . name , /*questionToken*/ undefined ,
109
- /*type*/ undefined , /*initializer*/ undefined ) ;
148
+ if ( ! assignmentExpr ) {
149
+ members . push ( createProperty ( [ ] , modifiers , symbol . name , /*questionToken*/ undefined ,
150
+ /*type*/ undefined , /*initializer*/ undefined ) ) ;
151
+ return members ;
110
152
}
111
153
112
- switch ( assignmentBinaryExpression . right . kind ) {
113
- case SyntaxKind . FunctionExpression : {
114
- const functionExpression = assignmentBinaryExpression . right as FunctionExpression ;
115
- const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( functionExpression , SyntaxKind . AsyncKeyword ) ) ;
116
- const method = createMethod ( /*decorators*/ undefined , fullModifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
117
- /*typeParameters*/ undefined , functionExpression . parameters , /*type*/ undefined , functionExpression . body ) ;
118
- copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ;
119
- return method ;
120
- }
154
+ // f.x = expr
155
+ if ( isPropertyAccessExpression ( memberDeclaration ) && ( isFunctionExpression ( assignmentExpr ) || isArrowFunction ( assignmentExpr ) ) ) {
156
+ return createFunctionLikeExpressionMember ( members , assignmentExpr , memberDeclaration . name ) ;
157
+ }
158
+ // f.prototype = { ... }
159
+ else if ( isObjectLiteralExpression ( assignmentExpr ) ) {
160
+ return flatMap (
161
+ assignmentExpr . properties ,
162
+ property => {
163
+ if ( isMethodDeclaration ( property ) || isGetOrSetAccessorDeclaration ( property ) ) {
164
+ // MethodDeclaration and AccessorDeclaration can appear in a class directly
165
+ return members . concat ( property ) ;
166
+ }
167
+ if ( isPropertyAssignment ( property ) && isFunctionExpression ( property . initializer ) ) {
168
+ return createFunctionLikeExpressionMember ( members , property . initializer , property . name ) ;
169
+ }
170
+ // Drop constructor assignments
171
+ if ( isConstructorAssignment ( property ) ) return members ;
172
+ return [ ] ;
173
+ }
174
+ ) ;
175
+ }
176
+ else {
177
+ // Don't try to declare members in JavaScript files
178
+ if ( isSourceFileJS ( sourceFile ) ) return members ;
179
+ if ( ! isPropertyAccessExpression ( memberDeclaration ) ) return members ;
180
+ const prop = createProperty ( /*decorators*/ undefined , modifiers , memberDeclaration . name , /*questionToken*/ undefined , /*type*/ undefined , assignmentExpr ) ;
181
+ copyLeadingComments ( assignmentBinaryExpression . parent , prop , sourceFile ) ;
182
+ members . push ( prop ) ;
183
+ return members ;
184
+ }
121
185
122
- case SyntaxKind . ArrowFunction : {
123
- const arrowFunction = assignmentBinaryExpression . right as ArrowFunction ;
124
- const arrowFunctionBody = arrowFunction . body ;
125
- let bodyBlock : Block ;
186
+ type MethodName = Parameters < typeof createMethod > [ 3 ] ;
126
187
127
- // case 1: () => { return [1,2,3] }
128
- if ( arrowFunctionBody . kind === SyntaxKind . Block ) {
129
- bodyBlock = arrowFunctionBody as Block ;
130
- }
131
- // case 2: () => [1,2,3]
132
- else {
133
- bodyBlock = createBlock ( [ createReturn ( arrowFunctionBody ) ] ) ;
134
- }
135
- const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( arrowFunction , SyntaxKind . AsyncKeyword ) ) ;
136
- const method = createMethod ( /*decorators*/ undefined , fullModifiers , /*asteriskToken*/ undefined , memberDeclaration . name , /*questionToken*/ undefined ,
137
- /*typeParameters*/ undefined , arrowFunction . parameters , /*type*/ undefined , bodyBlock ) ;
138
- copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ;
139
- return method ;
140
- }
188
+ function createFunctionLikeExpressionMember ( members : readonly ClassElement [ ] , expression : FunctionExpression | ArrowFunction , name : MethodName ) {
189
+ if ( isFunctionExpression ( expression ) ) return createFunctionExpressionMember ( members , expression , name ) ;
190
+ else return createArrowFunctionExpressionMember ( members , expression , name ) ;
191
+ }
141
192
142
- default : {
143
- // Don't try to declare members in JavaScript files
144
- if ( isSourceFileJS ( sourceFile ) ) {
145
- return ;
146
- }
147
- const prop = createProperty ( /*decorators*/ undefined , modifiers , memberDeclaration . name , /*questionToken*/ undefined ,
148
- /*type*/ undefined , assignmentBinaryExpression . right ) ;
149
- copyLeadingComments ( assignmentBinaryExpression . parent , prop , sourceFile ) ;
150
- return prop ;
193
+ function createFunctionExpressionMember ( members : readonly ClassElement [ ] , functionExpression : FunctionExpression , name : MethodName ) {
194
+ const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( functionExpression , SyntaxKind . AsyncKeyword ) ) ;
195
+ const method = createMethod ( /*decorators*/ undefined , fullModifiers , /*asteriskToken*/ undefined , name , /*questionToken*/ undefined ,
196
+ /*typeParameters*/ undefined , functionExpression . parameters , /*type*/ undefined , functionExpression . body ) ;
197
+ copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ;
198
+ return members . concat ( method ) ;
199
+ }
200
+
201
+ function createArrowFunctionExpressionMember ( members : readonly ClassElement [ ] , arrowFunction : ArrowFunction , name : MethodName ) {
202
+ const arrowFunctionBody = arrowFunction . body ;
203
+ let bodyBlock : Block ;
204
+
205
+ // case 1: () => { return [1,2,3] }
206
+ if ( arrowFunctionBody . kind === SyntaxKind . Block ) {
207
+ bodyBlock = arrowFunctionBody as Block ;
151
208
}
209
+ // case 2: () => [1,2,3]
210
+ else {
211
+ bodyBlock = createBlock ( [ createReturn ( arrowFunctionBody ) ] ) ;
212
+ }
213
+ const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( arrowFunction , SyntaxKind . AsyncKeyword ) ) ;
214
+ const method = createMethod ( /*decorators*/ undefined , fullModifiers , /*asteriskToken*/ undefined , name , /*questionToken*/ undefined ,
215
+ /*typeParameters*/ undefined , arrowFunction . parameters , /*type*/ undefined , bodyBlock ) ;
216
+ copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ;
217
+ return members . concat ( method ) ;
152
218
}
153
219
}
154
220
}
@@ -192,4 +258,10 @@ namespace ts.codefix {
192
258
function getModifierKindFromSource ( source : Node , kind : SyntaxKind ) : readonly Modifier [ ] | undefined {
193
259
return filter ( source . modifiers , modifier => modifier . kind === kind ) ;
194
260
}
261
+
262
+ function isConstructorAssignment ( x : ObjectLiteralElementLike | PropertyAccessExpression ) {
263
+ if ( ! x . name ) return false ;
264
+ if ( isIdentifier ( x . name ) && x . name . text === "constructor" ) return true ;
265
+ return false ;
266
+ }
195
267
}
0 commit comments