6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { SourceMapGenerator } from 'source-map' ;
10
9
import * as ts from 'typescript' ;
11
10
12
11
import { getDecoratorDeclarations } from './decorators' ;
13
- import { Rewriter } from './rewriter' ;
12
+ import { getIdentifierText , Rewriter } from './rewriter' ;
13
+ import { SourceMapper } from './source_map_utils' ;
14
14
import { assertTypeChecked , TypeTranslator } from './type-translator' ;
15
15
import { toArray } from './util' ;
16
16
17
- // ClassRewriter rewrites a single "class Foo {...}" declaration.
17
+ // DecoratorClassVisitor rewrites a single "class Foo {...}" declaration.
18
18
// It's its own object because we collect decorators on the class and the ctor
19
19
// separately for each class we encounter.
20
- class ClassRewriter extends Rewriter {
20
+ export class DecoratorClassVisitor {
21
21
/** Decorators on the class itself. */
22
22
decorators : ts . Decorator [ ] ;
23
23
/** The constructor parameter list and decorators on each param. */
24
24
ctorParameters : ( [ string | undefined , ts . Decorator [ ] | undefined ] | null ) [ ] ;
25
25
/** Per-method decorators. */
26
26
propDecorators : Map < string , ts . Decorator [ ] > ;
27
27
28
- constructor ( private typeChecker : ts . TypeChecker , sourceFile : ts . SourceFile ) {
29
- super ( sourceFile ) ;
28
+ constructor (
29
+ private typeChecker : ts . TypeChecker , private rewriter : Rewriter ,
30
+ private classDecl : ts . ClassDeclaration ) {
31
+ if ( classDecl . decorators ) {
32
+ let toLower = this . decoratorsToLower ( classDecl ) ;
33
+ if ( toLower . length > 0 ) this . decorators = toLower ;
34
+ }
30
35
}
31
36
32
37
/**
@@ -69,40 +74,6 @@ class ClassRewriter extends Rewriter {
69
74
return [ ] ;
70
75
}
71
76
72
- /**
73
- * process is the main entry point, rewriting a single class node.
74
- */
75
- process ( node : ts . ClassDeclaration ) : { output : string , diagnostics : ts . Diagnostic [ ] } {
76
- if ( node . decorators ) {
77
- let toLower = this . decoratorsToLower ( node ) ;
78
- if ( toLower . length > 0 ) this . decorators = toLower ;
79
- }
80
-
81
- // Emit the class contents, but stop just before emitting the closing curly brace.
82
- // (This code is the same as Rewriter.writeNode except for the curly brace handling.)
83
- let pos = node . getFullStart ( ) ;
84
- ts . forEachChild ( node , child => {
85
- // This forEachChild handles emitting the text between each child, while child.visit
86
- // recursively emits the children themselves.
87
- this . writeRange ( pos , child . getFullStart ( ) ) ;
88
- this . visit ( child ) ;
89
- pos = child . getEnd ( ) ;
90
- } ) ;
91
-
92
- // At this point, we've emitted up through the final child of the class, so all that
93
- // remains is the trailing whitespace and closing curly brace.
94
- // The final character owned by the class node should always be a ' }',
95
- // or we somehow got the AST wrong and should report an error.
96
- // (Any whitespace or semicolon following the '}' will be part of the next Node.)
97
- if ( this . file . text [ node . getEnd ( ) - 1 ] !== '}' ) {
98
- this . error ( node , 'unexpected class terminator' ) ;
99
- }
100
- this . writeRange ( pos , node . getEnd ( ) - 1 ) ;
101
- this . emitMetadata ( ) ;
102
- this . emit ( '}' ) ;
103
- return this . getOutput ( ) ;
104
- }
105
-
106
77
/**
107
78
* gatherConstructor grabs the parameter list and decorators off the class
108
79
* constructor, and emits nothing.
@@ -147,7 +118,7 @@ class ClassRewriter extends Rewriter {
147
118
if ( ! method . name || method . name . kind !== ts . SyntaxKind . Identifier ) {
148
119
// Method has a weird name, e.g.
149
120
// [Symbol.foo]() {...}
150
- this . error ( method , 'cannot process decorators on strangely named method' ) ;
121
+ this . rewriter . error ( method , 'cannot process decorators on strangely named method' ) ;
151
122
return ;
152
123
}
153
124
@@ -158,153 +129,227 @@ class ClassRewriter extends Rewriter {
158
129
this . propDecorators . set ( name , decorators ) ;
159
130
}
160
131
161
- /**
162
- * maybeProcess is called by the traversal of the AST.
163
- * @return True if the node was handled, false to have the node emitted as normal.
164
- */
165
- protected maybeProcess ( node : ts . Node ) : boolean {
132
+ beforeProcessNode ( node : ts . Node ) {
166
133
switch ( node . kind ) {
167
- case ts . SyntaxKind . ClassDeclaration :
168
- // Encountered a new class while processing this class; use a new separate
169
- // rewriter to gather+emit its metadata.
170
- let { output, diagnostics} =
171
- new ClassRewriter ( this . typeChecker , this . file ) . process ( node as ts . ClassDeclaration ) ;
172
- this . diagnostics . push ( ...diagnostics ) ;
173
- this . emit ( output ) ;
174
- return true ;
175
134
case ts . SyntaxKind . Constructor :
176
135
this . gatherConstructor ( node as ts . ConstructorDeclaration ) ;
177
- return false ; // Proceed with ordinary emit of the ctor.
136
+ break ;
178
137
case ts . SyntaxKind . PropertyDeclaration :
179
138
case ts . SyntaxKind . SetAccessor :
180
139
case ts . SyntaxKind . GetAccessor :
181
140
case ts . SyntaxKind . MethodDeclaration :
182
141
this . gatherMethodOrProperty ( node as ts . Declaration ) ;
183
- return false ; // Proceed with ordinary emit of the method.
184
- case ts . SyntaxKind . Decorator :
185
- if ( this . shouldLower ( node as ts . Decorator ) ) {
186
- // Return true to signal that this node should not be emitted,
187
- // but still emit the whitespace *before* the node.
188
- this . writeRange ( node . getFullStart ( ) , node . getStart ( ) ) ;
189
- return true ;
190
- }
191
- return false ;
142
+ break ;
192
143
default :
193
- return false ;
144
+ }
145
+ }
146
+
147
+ maybeProcessDecorator ( node : ts . Node , start ?: number ) : boolean {
148
+ if ( this . shouldLower ( node as ts . Decorator ) ) {
149
+ // Return true to signal that this node should not be emitted,
150
+ // but still emit the whitespace *before* the node.
151
+ if ( ! start ) {
152
+ start = node . getFullStart ( ) ;
153
+ }
154
+ this . rewriter . writeRange ( node , start , node . getStart ( ) ) ;
155
+ return true ;
156
+ }
157
+ return false ;
158
+ }
159
+
160
+ /**
161
+ * emits the types for the various gathered metadata to be used
162
+ * in the tsickle type annotations helper.
163
+ */
164
+ emitMetadataTypeAnnotationsHelpers ( ) {
165
+ if ( ! this . classDecl . name ) return ;
166
+ let className = getIdentifierText ( this . classDecl . name ) ;
167
+ if ( this . decorators ) {
168
+ this . rewriter . emit ( `/** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */\n` ) ;
169
+ this . rewriter . emit ( `${ className } .decorators;\n` ) ;
170
+ }
171
+ if ( this . decorators || this . ctorParameters ) {
172
+ this . rewriter . emit ( `/**\n` ) ;
173
+ this . rewriter . emit ( ` * @nocollapse\n` ) ;
174
+ this . rewriter . emit (
175
+ ` * @type {function(): !Array<(null|{type: ?, decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>)})>}\n` ) ;
176
+ this . rewriter . emit ( ` */\n` ) ;
177
+ this . rewriter . emit ( `${ className } .ctorParameters;\n` ) ;
178
+ }
179
+ if ( this . propDecorators ) {
180
+ this . rewriter . emit (
181
+ `/** @type {!Object<string,!Array<{type: !Function, args: (undefined|!Array<?>)}>>} */\n` ) ;
182
+ this . rewriter . emit ( `${ className } .propDecorators;\n` ) ;
194
183
}
195
184
}
196
185
197
186
/**
198
- * emitMetadata emits the various gathered metadata, as static fields.
187
+ * emits the various gathered metadata, as static fields.
199
188
*/
200
- private emitMetadata ( ) {
189
+ emitMetadataAsStaticProperties ( ) {
201
190
const decoratorInvocations = '{type: Function, args?: any[]}[]' ;
202
191
if ( this . decorators ) {
203
- this . emit ( `static decorators: ${ decoratorInvocations } = [\n` ) ;
192
+ this . rewriter . emit ( `static decorators: ${ decoratorInvocations } = [\n` ) ;
204
193
for ( let annotation of this . decorators ) {
205
194
this . emitDecorator ( annotation ) ;
206
- this . emit ( ',\n' ) ;
195
+ this . rewriter . emit ( ',\n' ) ;
207
196
}
208
- this . emit ( '];\n' ) ;
197
+ this . rewriter . emit ( '];\n' ) ;
209
198
}
210
199
211
200
if ( this . decorators || this . ctorParameters ) {
212
- this . emit ( `/** @nocollapse */\n` ) ;
201
+ this . rewriter . emit ( `/** @nocollapse */\n` ) ;
213
202
// ctorParameters may contain forward references in the type: field, so wrap in a function
214
203
// closure
215
- this . emit (
204
+ this . rewriter . emit (
216
205
`static ctorParameters: () => ({type: any, decorators?: ` + decoratorInvocations +
217
206
`}|null)[] = () => [\n` ) ;
218
207
for ( let param of this . ctorParameters || [ ] ) {
219
208
if ( ! param ) {
220
- this . emit ( 'null,\n' ) ;
209
+ this . rewriter . emit ( 'null,\n' ) ;
221
210
continue ;
222
211
}
223
212
let [ ctor , decorators ] = param ;
224
- this . emit ( `{type: ${ ctor } , ` ) ;
213
+ this . rewriter . emit ( `{type: ${ ctor } , ` ) ;
225
214
if ( decorators ) {
226
- this . emit ( 'decorators: [' ) ;
215
+ this . rewriter . emit ( 'decorators: [' ) ;
227
216
for ( let decorator of decorators ) {
228
217
this . emitDecorator ( decorator ) ;
229
- this . emit ( ', ' ) ;
218
+ this . rewriter . emit ( ', ' ) ;
230
219
}
231
- this . emit ( ']' ) ;
220
+ this . rewriter . emit ( ']' ) ;
232
221
}
233
- this . emit ( '},\n' ) ;
222
+ this . rewriter . emit ( '},\n' ) ;
234
223
}
235
- this . emit ( `];\n` ) ;
224
+ this . rewriter . emit ( `];\n` ) ;
236
225
}
237
226
238
227
if ( this . propDecorators ) {
239
- this . emit ( `static propDecorators: {[key: string]: ` + decoratorInvocations + `} = {\n` ) ;
228
+ this . rewriter . emit (
229
+ `static propDecorators: {[key: string]: ` + decoratorInvocations + `} = {\n` ) ;
240
230
for ( let name of toArray ( this . propDecorators . keys ( ) ) ) {
241
- this . emit ( `'${ name } ': [` ) ;
231
+ this . rewriter . emit ( `'${ name } ': [` ) ;
242
232
243
233
for ( let decorator of this . propDecorators . get ( name ) ! ) {
244
234
this . emitDecorator ( decorator ) ;
245
- this . emit ( ',' ) ;
235
+ this . rewriter . emit ( ',' ) ;
246
236
}
247
- this . emit ( '],\n' ) ;
237
+ this . rewriter . emit ( '],\n' ) ;
248
238
}
249
- this . emit ( '};\n' ) ;
239
+ this . rewriter . emit ( '};\n' ) ;
250
240
}
251
241
}
252
242
253
243
private emitDecorator ( decorator : ts . Decorator ) {
254
- this . emit ( '{ type: ' ) ;
244
+ this . rewriter . emit ( '{ type: ' ) ;
255
245
let expr = decorator . expression ;
256
246
switch ( expr . kind ) {
257
247
case ts . SyntaxKind . Identifier :
258
248
// The decorator was a plain @Foo .
259
- this . visit ( expr ) ;
249
+ this . rewriter . visit ( expr ) ;
260
250
break ;
261
251
case ts . SyntaxKind . CallExpression :
262
252
// The decorator was a call, like @Foo(bar).
263
253
let call = expr as ts . CallExpression ;
264
- this . visit ( call . expression ) ;
254
+ this . rewriter . visit ( call . expression ) ;
265
255
if ( call . arguments . length ) {
266
- this . emit ( ', args: [' ) ;
256
+ this . rewriter . emit ( ', args: [' ) ;
267
257
for ( let arg of call . arguments ) {
268
- this . emit ( arg . getText ( ) ) ;
269
- this . emit ( ', ' ) ;
258
+ this . rewriter . emit ( arg . getText ( ) ) ;
259
+ this . rewriter . emit ( ', ' ) ;
270
260
}
271
- this . emit ( ']' ) ;
261
+ this . rewriter . emit ( ']' ) ;
272
262
}
273
263
break ;
274
264
default :
275
- this . errorUnimplementedKind ( expr , 'gathering metadata' ) ;
276
- this . emit ( 'undefined' ) ;
265
+ this . rewriter . errorUnimplementedKind ( expr , 'gathering metadata' ) ;
266
+ this . rewriter . emit ( 'undefined' ) ;
277
267
}
278
- this . emit ( ' }' ) ;
268
+ this . rewriter . emit ( ' }' ) ;
279
269
}
280
270
}
281
271
282
272
class DecoratorRewriter extends Rewriter {
283
- constructor ( private typeChecker : ts . TypeChecker , sourceFile : ts . SourceFile ) {
284
- super ( sourceFile ) ;
273
+ /** ComposableDecoratorRewriter when using tsickle as a TS transformer */
274
+ private currentDecoratorConverter : DecoratorClassVisitor ;
275
+
276
+ constructor (
277
+ private typeChecker : ts . TypeChecker , file : ts . SourceFile , sourceMapper ?: SourceMapper ) {
278
+ super ( file , sourceMapper ) ;
285
279
}
286
280
287
- process ( ) : { output : string , diagnostics : ts . Diagnostic [ ] , sourceMap : SourceMapGenerator } {
281
+ process ( ) : { output : string , diagnostics : ts . Diagnostic [ ] } {
288
282
this . visit ( this . file ) ;
289
283
return this . getOutput ( ) ;
290
284
}
291
285
292
286
protected maybeProcess ( node : ts . Node ) : boolean {
287
+ if ( this . currentDecoratorConverter ) {
288
+ this . currentDecoratorConverter . beforeProcessNode ( node ) ;
289
+ }
293
290
switch ( node . kind ) {
291
+ case ts . SyntaxKind . Decorator :
292
+ return this . currentDecoratorConverter &&
293
+ this . currentDecoratorConverter . maybeProcessDecorator ( node ) ;
294
294
case ts . SyntaxKind . ClassDeclaration :
295
- let { output, diagnostics} =
296
- new ClassRewriter ( this . typeChecker , this . file ) . process ( node as ts . ClassDeclaration ) ;
297
- this . diagnostics . push ( ...diagnostics ) ;
298
- this . emit ( output ) ;
295
+ const oldDecoratorConverter = this . currentDecoratorConverter ;
296
+ this . currentDecoratorConverter =
297
+ new DecoratorClassVisitor ( this . typeChecker , this , node as ts . ClassDeclaration ) ;
298
+ this . writeRange ( node , node . getFullStart ( ) , node . getStart ( ) ) ;
299
+ visitClassContent ( node as ts . ClassDeclaration , this , this . currentDecoratorConverter ) ;
300
+ this . currentDecoratorConverter = oldDecoratorConverter ;
299
301
return true ;
300
302
default :
301
303
return false ;
302
304
}
303
305
}
304
306
}
305
307
306
- export function convertDecorators ( typeChecker : ts . TypeChecker , sourceFile : ts . SourceFile ) :
307
- { output : string , diagnostics : ts . Diagnostic [ ] , sourceMap : SourceMapGenerator } {
308
+ export function convertDecorators (
309
+ typeChecker : ts . TypeChecker , sourceFile : ts . SourceFile ,
310
+ sourceMapper ?: SourceMapper ) : { output : string , diagnostics : ts . Diagnostic [ ] } {
308
311
assertTypeChecked ( sourceFile ) ;
309
- return new DecoratorRewriter ( typeChecker , sourceFile ) . process ( ) ;
312
+ return new DecoratorRewriter ( typeChecker , sourceFile , sourceMapper ) . process ( ) ;
313
+ }
314
+
315
+ export function visitClassContent (
316
+ classDecl : ts . ClassDeclaration , rewriter : Rewriter , decoratorVisitor ?: DecoratorClassVisitor ) {
317
+ let pos = classDecl . getStart ( ) ;
318
+ if ( decoratorVisitor ) {
319
+ // strip out decorators if needed
320
+ ts . forEachChild ( classDecl , child => {
321
+ if ( child . kind !== ts . SyntaxKind . Decorator ) {
322
+ return ;
323
+ }
324
+ // Note: The getFullStart() of the first decorator is the same
325
+ // as the getFullStart() of the class declaration.
326
+ // Therefore, we need to use Math.max to not print the whitespace
327
+ // of the class again.
328
+ const childStart = Math . max ( pos , child . getFullStart ( ) ) ;
329
+ rewriter . writeRange ( classDecl , pos , childStart ) ;
330
+ if ( decoratorVisitor . maybeProcessDecorator ( child , childStart ) ) {
331
+ pos = child . getEnd ( ) ;
332
+ }
333
+ } ) ;
334
+ }
335
+ if ( classDecl . members . length > 0 ) {
336
+ rewriter . writeRange ( classDecl , pos , classDecl . members [ 0 ] . getFullStart ( ) ) ;
337
+ for ( let member of classDecl . members ) {
338
+ rewriter . visit ( member ) ;
339
+ }
340
+ pos = classDecl . getLastToken ( ) . getFullStart ( ) ;
341
+ }
342
+ // At this point, we've emitted up through the final child of the class, so all that
343
+ // remains is the trailing whitespace and closing curly brace.
344
+ // The final character owned by the class node should always be a ' }',
345
+ // or we somehow got the AST wrong and should report an error.
346
+ // (Any whitespace or semicolon following the '}' will be part of the next Node.)
347
+ if ( rewriter . file . text [ classDecl . getEnd ( ) - 1 ] !== '}' ) {
348
+ rewriter . error ( classDecl , 'unexpected class terminator' ) ;
349
+ }
350
+ rewriter . writeRange ( classDecl , pos , classDecl . getEnd ( ) - 1 ) ;
351
+ if ( decoratorVisitor ) {
352
+ decoratorVisitor . emitMetadataAsStaticProperties ( ) ;
353
+ }
354
+ rewriter . emit ( '}' ) ;
310
355
}
0 commit comments