@@ -202,49 +202,31 @@ export function decoratorDownlevelTransformer(
202202 diagnostics : ts . Diagnostic [ ] ,
203203) : ( context : ts . TransformationContext ) => ts . Transformer < ts . SourceFile > {
204204 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 > ( ) ;
207206
208207 /**
209208 * Converts an EntityName (from a type annotation) to an expression (accessing a value).
210209 *
211210 * For a given ts.EntityName, this walks depth first to find the leftmost ts.Identifier, then
212211 * converts the path into property accesses.
213212 *
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.
222213 */
223214 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-
238215 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 ) ;
244219 }
245220
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 ;
248230 }
249231 const ref = entityNameToExpression ( name . left ) ;
250232 if ( ! ref ) {
@@ -254,17 +236,13 @@ export function decoratorDownlevelTransformer(
254236 return ts . createPropertyAccess ( ref , name . right ) ;
255237 }
256238
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+ }
265243
266244 const parametersInfo : ParameterDecorationInfo [ ] = [ ] ;
267- for ( const param of ctor . parameters ) {
245+ for ( const param of node . parameters ) {
268246 const paramInfo : ParameterDecorationInfo = { decorators : [ ] , type : null } ;
269247
270248 for ( const decorator of param . decorators || [ ] ) {
@@ -280,98 +258,56 @@ export function decoratorDownlevelTransformer(
280258 parametersInfo . push ( paramInfo ) ;
281259 }
282260
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+ ) ;
305267
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 ;
317271 }
272+ }
318273
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 ) ,
324284 ) ;
285+ } else {
286+ return ts . visitEachChild ( node , visitor , context ) ;
325287 }
326-
327- newClassDeclaration . members = ts . setTextRange (
328- ts . createNodeArray ( newMembers , newClassDeclaration . members . hasTrailingComma ) ,
329- classDecl . members ,
330- ) ;
331-
332- return newClassDeclaration ;
333288 }
334289
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 ) ;
362304 }
363305 }
364306
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+ } ;
376312 } ;
377313}
0 commit comments