@@ -202,49 +202,31 @@ export function decoratorDownlevelTransformer(
202
202
diagnostics : ts . Diagnostic [ ] ,
203
203
) : ( context : ts . TransformationContext ) => ts . Transformer < ts . SourceFile > {
204
204
return ( context : ts . TransformationContext ) => {
205
- /** A map from symbols to the identifier of an import, reset per SourceFile. */
206
- let importNamesBySymbol = new Map < ts . Symbol , ts . Identifier > ( ) ;
205
+ const parameterTypeSymbols = new Set < ts . Symbol > ( ) ;
207
206
208
207
/**
209
208
* Converts an EntityName (from a type annotation) to an expression (accessing a value).
210
209
*
211
210
* For a given ts.EntityName, this walks depth first to find the leftmost ts.Identifier, then
212
211
* converts the path into property accesses.
213
212
*
214
- * This generally works, but TypeScript's emit pipeline does not serialize identifiers that are
215
- * only used in a type location (such as identifiers in a TypeNode), even if the identifier
216
- * itself points to a value (e.g. a class). To avoid that problem, this method finds the symbol
217
- * representing the identifier (using typeChecker), then looks up where it was imported (using
218
- * importNamesBySymbol), and then uses the imported name instead of the identifier from the type
219
- * expression, if any. Otherwise it'll use the identifier unchanged. This makes sure the
220
- * identifier is not marked as stemming from a "type only" expression, causing it to be emitted
221
- * and causing the import to be retained.
222
213
*/
223
214
function entityNameToExpression ( name : ts . EntityName ) : ts . Expression | undefined {
224
- const sym = typeChecker . getSymbolAtLocation ( name ) ;
225
- if ( ! sym ) {
226
- return undefined ;
227
- }
228
- // Check if the entity name references a symbol that is an actual value. If it is not, it
229
- // cannot be referenced by an expression, so return undefined.
230
- let symToCheck = sym ;
231
- if ( symToCheck . flags & ts . SymbolFlags . Alias ) {
232
- symToCheck = typeChecker . getAliasedSymbol ( symToCheck ) ;
233
- }
234
- if ( ! ( symToCheck . flags & ts . SymbolFlags . Value ) ) {
235
- return undefined ;
236
- }
237
-
238
215
if ( ts . isIdentifier ( name ) ) {
239
- // If there's a known import name for this symbol, use it so that the import will be
240
- // retained and the value can be referenced.
241
- const value = importNamesBySymbol . get ( sym ) ;
242
- if ( value ) {
243
- return value ;
216
+ const typeSymbol = typeChecker . getSymbolAtLocation ( name ) ;
217
+ if ( typeSymbol ) {
218
+ parameterTypeSymbols . add ( typeSymbol ) ;
244
219
}
245
220
246
- // Otherwise this will be a locally declared name, just return that.
247
- return name ;
221
+ // Based on TS's strategy to allow the checker to reach this identifier
222
+ // tslint:disable-next-line:max-line-length
223
+ // https://github.com/microsoft/TypeScript/blob/7f47a08a5e9874f0f97a667bd81eebddec61247c/src/compiler/transformers/ts.ts#L2093
224
+ const exp = ts . getMutableClone ( name ) ;
225
+ exp . flags &= ~ ts . NodeFlags . Synthesized ;
226
+ ( ( exp as unknown ) as { original : undefined } ) . original = undefined ;
227
+ exp . parent = ts . getParseTreeNode ( name . getSourceFile ( ) ) ;
228
+
229
+ return exp ;
248
230
}
249
231
const ref = entityNameToExpression ( name . left ) ;
250
232
if ( ! ref ) {
@@ -254,17 +236,13 @@ export function decoratorDownlevelTransformer(
254
236
return ts . createPropertyAccess ( ref , name . right ) ;
255
237
}
256
238
257
- /**
258
- * Transforms a constructor. Returns the transformed constructor and the list of parameter
259
- * information collected, consisting of decorators and optional type.
260
- */
261
- function transformConstructor (
262
- ctor : ts . ConstructorDeclaration ,
263
- ) : [ ts . ConstructorDeclaration , ParameterDecorationInfo [ ] ] {
264
- ctor = ts . visitEachChild ( ctor , visitor , context ) ;
239
+ function classMemberVisitor ( node : ts . Node ) : ts . VisitResult < ts . Node > {
240
+ if ( ! ts . isConstructorDeclaration ( node ) || ! node . body ) {
241
+ return visitor ( node ) ;
242
+ }
265
243
266
244
const parametersInfo : ParameterDecorationInfo [ ] = [ ] ;
267
- for ( const param of ctor . parameters ) {
245
+ for ( const param of node . parameters ) {
268
246
const paramInfo : ParameterDecorationInfo = { decorators : [ ] , type : null } ;
269
247
270
248
for ( const decorator of param . decorators || [ ] ) {
@@ -280,98 +258,56 @@ export function decoratorDownlevelTransformer(
280
258
parametersInfo . push ( paramInfo ) ;
281
259
}
282
260
283
- return [ ctor , parametersInfo ] ;
284
- }
285
-
286
- /**
287
- * Transforms a single class declaration:
288
- * - creates a ctorParameters property
289
- */
290
- function transformClassDeclaration ( classDecl : ts . ClassDeclaration ) : ts . ClassDeclaration {
291
- if ( ! classDecl . decorators || classDecl . decorators . length === 0 ) {
292
- return classDecl ;
293
- }
294
-
295
- const newMembers : ts . ClassElement [ ] = [ ] ;
296
- let classParameters : ParameterDecorationInfo [ ] | null = null ;
297
-
298
- for ( const member of classDecl . members ) {
299
- switch ( member . kind ) {
300
- case ts . SyntaxKind . Constructor : {
301
- const ctor = member as ts . ConstructorDeclaration ;
302
- if ( ! ctor . body ) {
303
- break ;
304
- }
261
+ if ( parametersInfo . length > 0 ) {
262
+ const ctorProperty = createCtorParametersClassProperty (
263
+ diagnostics ,
264
+ entityNameToExpression ,
265
+ parametersInfo ,
266
+ ) ;
305
267
306
- const [ newMember , parametersInfo ] = transformConstructor (
307
- member as ts . ConstructorDeclaration ,
308
- ) ;
309
- classParameters = parametersInfo ;
310
- newMembers . push ( newMember ) ;
311
- continue ;
312
- }
313
- default :
314
- break ;
315
- }
316
- newMembers . push ( ts . visitEachChild ( member , visitor , context ) ) ;
268
+ return [ node , ctorProperty ] ;
269
+ } else {
270
+ return node ;
317
271
}
272
+ }
318
273
319
- const newClassDeclaration = ts . getMutableClone ( classDecl ) ;
320
-
321
- if ( classParameters ) {
322
- newMembers . push (
323
- createCtorParametersClassProperty ( diagnostics , entityNameToExpression , classParameters ) ,
274
+ function visitor < T extends ts . Node > ( node : T ) : ts . Node {
275
+ if ( ts . isClassDeclaration ( node ) ) {
276
+ return ts . updateClassDeclaration (
277
+ node ,
278
+ node . decorators ,
279
+ node . modifiers ,
280
+ node . name ,
281
+ node . typeParameters ,
282
+ node . heritageClauses ,
283
+ ts . visitNodes ( node . members , classMemberVisitor ) ,
324
284
) ;
285
+ } else {
286
+ return ts . visitEachChild ( node , visitor , context ) ;
325
287
}
326
-
327
- newClassDeclaration . members = ts . setTextRange (
328
- ts . createNodeArray ( newMembers , newClassDeclaration . members . hasTrailingComma ) ,
329
- classDecl . members ,
330
- ) ;
331
-
332
- return newClassDeclaration ;
333
288
}
334
289
335
- function visitor ( node : ts . Node ) : ts . Node {
336
- switch ( node . kind ) {
337
- case ts . SyntaxKind . SourceFile : {
338
- importNamesBySymbol = new Map < ts . Symbol , ts . Identifier > ( ) ;
339
-
340
- return ts . visitEachChild ( node , visitor , context ) ;
341
- }
342
- case ts . SyntaxKind . ImportDeclaration : {
343
- const impDecl = node as ts . ImportDeclaration ;
344
- if ( impDecl . importClause ) {
345
- const importClause = impDecl . importClause ;
346
- const names = [ ] ;
347
- if ( importClause . name ) {
348
- names . push ( importClause . name ) ;
349
- }
350
- if (
351
- importClause . namedBindings &&
352
- importClause . namedBindings . kind === ts . SyntaxKind . NamedImports
353
- ) {
354
- const namedImports = importClause . namedBindings as ts . NamedImports ;
355
- names . push ( ...namedImports . elements . map ( e => e . name ) ) ;
356
- }
357
- for ( const name of names ) {
358
- const sym = typeChecker . getSymbolAtLocation ( name ) ;
359
- if ( sym ) {
360
- importNamesBySymbol . set ( sym , name ) ;
361
- }
290
+ return ( sf : ts . SourceFile ) => {
291
+ parameterTypeSymbols . clear ( ) ;
292
+
293
+ return ts . visitEachChild (
294
+ visitor ( sf ) as ts . SourceFile ,
295
+ function visitImports ( node : ts . Node ) : ts . Node {
296
+ if (
297
+ ( ts . isImportSpecifier ( node ) || ts . isNamespaceImport ( node ) || ts . isImportClause ( node ) ) &&
298
+ node . name
299
+ ) {
300
+ const importSymbol = typeChecker . getSymbolAtLocation ( node . name ) ;
301
+ if ( importSymbol && parameterTypeSymbols . has ( importSymbol ) ) {
302
+ // Using a clone prevents TS from removing the import specifier
303
+ return ts . getMutableClone ( node ) ;
362
304
}
363
305
}
364
306
365
- return ts . visitEachChild ( node , visitor , context ) ;
366
- }
367
- case ts . SyntaxKind . ClassDeclaration : {
368
- return transformClassDeclaration ( node as ts . ClassDeclaration ) ;
369
- }
370
- default :
371
- return ts . visitEachChild ( node , visitor , context ) ;
372
- }
373
- }
374
-
375
- return ( sf : ts . SourceFile ) => visitor ( sf ) as ts . SourceFile ;
307
+ return ts . visitEachChild ( node , visitImports , context ) ;
308
+ } ,
309
+ context ,
310
+ ) ;
311
+ } ;
376
312
} ;
377
313
}
0 commit comments