import * as ts from 'typescript' ; export const enum GenerateNameStrategy { PrependPrefixOnly = 'prependPrefixOnly' , Random = 'random' , RandomStable = 'randomStable' } export interface PropertyMinifierOptions { /**
     * Prefix of generated names (e.g. '__private__')
     */ prefix : string ; } const defaultOptions = { prefix : '_private_' , } ; type NodeCreator < T extends ts . Node > = ( newName : string ) => T ; export class PropertiesMinifier { private readonly options : PropertyMinifierOptions ; public constructor ( options ? ) { this . options = { ... defaultOptions , ... options } ; } public visitSourceFile ( node , program , context ) { const result = this . visitNodeAndChildren ( node , program , context ) ; return result ; } private visitNodeAndChildren ( node , program , context ) ; private visitNodeAndChildren ( node , program , context ) ; private visitNodeAndChildren ( node , program , context ) { return ts . visitEachChild ( this . visitNode ( node , program ) , ( childNode ) => this . visitNodeAndChildren ( childNode , program , context ) , context ) ; } private visitNode ( node , program ) { if ( isAccessExpression ( node ) ) { return this . createNewAccessExpression ( node , program ) ; } else if ( ts . isBindingElement ( node ) ) { return this . createNewBindingElement ( node , program ) ; } else if ( isConstructorParameterReference ( node , program ) ) { return this . createNewNode ( program , node , ts . createIdentifier ) ; } return node ; } private createNewAccessExpression ( node , program ) { const typeChecker = program . getTypeChecker ( ) ; const accessName = ts . isPropertyAccessExpression ( node ) ? node . name : node . argumentExpression ; const symbol = typeChecker . getSymbolAtLocation ( accessName ) ; if ( ! isPrivateNonStaticClassMember ( symbol ) ) { return node ; } let propName ; let creator ; if ( ts . isPropertyAccessExpression ( node ) ) { propName = node . name ; creator = ( newName ) => { return ts . createPropertyAccess ( node . expression , newName ) ; } ; } else { if ( ! ts . isStringLiteral ( node . argumentExpression ) ) { return node ; } propName = node . argumentExpression ; creator = ( newName ) => { return ts . createElementAccess ( node . expression , ts . createStringLiteral ( newName ) ) ; } ; } return this . createNewNode ( program , propName , creator ) ; } private createNewBindingElement ( node , program ) { const typeChecker = program . getTypeChecker ( ) ; let propName ; let symbol ; if ( node . propertyName === undefined ) { if ( ! ts . isObjectBindingPattern ( node . parent as ts . Node ) ) { return node ; } const type = typeChecker . getTypeAtLocation ( node . parent as ts . Node ) ; if ( type . isUnion ( ) ) { return node ; } if ( ! ts . isIdentifier ( node . name ) ) { return node ; } propName = node . name ; symbol = type . getProperty ( ts . idText ( propName ) ) ; } else { propName = node . propertyName ; symbol = typeChecker . getSymbolAtLocation ( node . propertyName ) ; } if ( ! isPrivateNonStaticClassMember ( symbol ) ) { return node ; } return this . createNewNode ( program , propName , ( newName ) => { return ts . createBindingElement ( node . dotDotDotToken , newName , node . name , node . initializer ) ; } ) ; } private createNewNode < T extends ts . Node > ( program , oldProperty , createNode ) { const typeChecker = program . getTypeChecker ( ) ; const symbol = typeChecker . getSymbolAtLocation ( oldProperty ) ; if ( symbol === undefined ) { throw new Error ( `Cannot get symbol for node "${ oldProperty . getText ( ) }"` ) ; } const oldPropertyName = symbol . escapedName as string ; const newPropertyName = this . getNewName ( oldPropertyName ) ; const newProperty = createNode ( newPropertyName ) ; return newProperty ; } private getNewName ( originalName ) { return `${ this . options . prefix }${ originalName }` ; } } function isPrivateNonStatic ( node ) { return hasPrivateKeyword ( node ) && ! hasModifier ( node , ts . SyntaxKind . StaticKeyword ) ; } function hasPrivateKeyword ( node ) { return hasModifier ( node , ts . SyntaxKind . PrivateKeyword ) ; } function hasModifier ( node , modifier ) { return node . modifiers !== undefined && node . modifiers . some ( ( mod ) => mod . kind === modifier ) ; } type AccessExpression = ts . PropertyAccessExpression | ts . ElementAccessExpression ; function isAccessExpression ( node ) { return ts . isPropertyAccessExpression ( node ) || ts . isElementAccessExpression ( node ) ; } type ClassMember = ts . MethodDeclaration | ts . PropertyDeclaration ; function isClassMember ( node ) { return ts . isMethodDeclaration ( node ) || ts . isPropertyDeclaration ( node ) ; } function isConstructorParameter ( node ) { return ts . isParameter ( node ) && ts . isConstructorDeclaration ( node . parent as ts . Node ) ; } function isConstructorParameterReference ( node , program ) { if ( ! ts . isIdentifier ( node ) ) { return false ; } const typeChecker = program . getTypeChecker ( ) ; const symbol = typeChecker . getSymbolAtLocation ( node ) ; return isPrivateNonStaticClassMember ( symbol ) ; } function isPrivateNonStaticClassMember ( symbol ) { if ( symbol === undefined || symbol . declarations === undefined ) { return false ; } return symbol . declarations . some ( ( x ) => { return ( isClassMember ( x ) && ! hasDecorators ( x ) || isConstructorParameter ( x ) ) && isPrivateNonStatic ( x ) ; } ) ; } function hasDecorators ( node ) { return ! ! node . decorators ; }