import * as ts from "typescript" ; import * as tse from "./typescript-extended" ; import { REFLECTIVE_KEY } from "../runtime/common" ; import { makeLiteral } from "./makeLiteral" ; import { getReflect } from "./reflect" ; import { Ctx , ScopeType } from "./types" ; function getSymbolId ( symb ) { return ( ( symb as any ) as { id : number ; } ) . id ; } function shouldReflect ( checker , node ) { const type = checker . getTypeAtLocation ( node . expression ) ; return Boolean ( getPropertyInMaybeUnion ( checker , type , REFLECTIVE_KEY ) ) ; } function getPropertyInMaybeUnion ( checker , type , propertyName ) { let typesToCheck ; if ( type . flags & ts . TypeFlags . UnionOrIntersection ) { typesToCheck = ( type as ts . UnionOrIntersectionType ) . types ; } else { typesToCheck = [ type ] ; } return typesToCheck . find ( type => Boolean ( checker . getPropertyOfType ( type , propertyName ) ) ) ; } function patchEmitResolver ( checker , context ) { let ReferencedSet = new Set < number > ( ) ; const markReferenced = ( symb ) => ReferencedSet . add ( getSymbolId ( symb ) ) ; const emitResolver = ( < tse . TransformationContext > context ) . getEmitResolver ( ) ; const oldIsReferenced = emitResolver . isReferencedAliasDeclaration ; emitResolver . isReferencedAliasDeclaration = function ( node , checkChildren ? ) { const res = oldIsReferenced ( node , checkChildren ) ; if ( res === true ) { return true ; } if ( node . kind === ts . SyntaxKind . ImportSpecifier ) { const name = ( < ts . ImportSpecifier > node ) . name ; const origSymb = checker . getAliasedSymbol ( checker . getSymbolAtLocation ( name ) ! ) ; return ReferencedSet . has ( getSymbolId ( origSymb ) ) ; } return true ; } ; const onNewSourceFile = ( ) => { ReferencedSet . clear ( ) ; } ; return { markReferenced , onNewSourceFile } ; } function Transformer ( program , context ) { let currentScope ; const checker = program . getTypeChecker ( ) ; const { markReferenced , onNewSourceFile } = patchEmitResolver ( checker , context ) ; const createContext = ( node ) => new Ctx ( checker , node , currentScope , markReferenced ) ; function visitCallExperssion ( node ) { const visitNodeChildren = ( ) => ts . visitEachChild ( node , visitor , context ) ; if ( ! shouldReflect ( checker , node ) ) { return visitNodeChildren ( ) ; } const ctx = createContext ( node ) ; const fnTypeNode = ( node . typeArguments || [ ] ) [ 0 ] ; if ( ! fnTypeNode ) { ctx . reportWarning ( 'is reflective but don\'t have generic type argument, see docs' ) ; return visitNodeChildren ( ) ; } const type = checker . getTypeAtLocation ( fnTypeNode ) ; const reflectedType = getReflect ( ctx ) . reflectType ( type ) ; const literal = makeLiteral ( reflectedType ) ; const newExpression = ts . createCall ( node . expression , undefined , [ literal ] ) ; return ts . updateCall ( node , newExpression , node . typeArguments , ts . visitNodes ( node . arguments , visitor ) ) ; } function visitDecotaror ( node ) { if ( ! shouldReflect ( checker , node ) ) { return node ; } const ctx = createContext ( node ) ; const classDeclaration = node . parent ; const type = checker . getTypeAtLocation ( classDeclaration ) ; if ( classDeclaration . kind !== ts . SyntaxKind . ClassDeclaration ) { ctx . reportWarning ( 'cant find decorator\'s class declaration' ) ; return node ; } const reflectedType = getReflect ( ctx ) . reflectClass ( < ts . InterfaceTypeWithDeclaredMembers > type ) ; const literal = makeLiteral ( reflectedType ) ; const newExpression = ts . createCall ( node . expression , undefined , [ literal ] ) ; return ts . updateDecorator ( node , newExpression ) ; } function onBeforeVisitNode ( node ) { switch ( node . kind ) { case ts . SyntaxKind . SourceFile : case ts . SyntaxKind . ModuleBlock : currentScope = < ScopeType > node ; break ; } } function visitor ( node ) { onBeforeVisitNode ( node ) ; switch ( node . kind ) { case ts . SyntaxKind . Decorator : return visitDecotaror ( < ts . Decorator > node ) ; case ts . SyntaxKind . CallExpression : return visitCallExperssion ( < ts . CallExpression > node ) ; default : return ts . visitEachChild ( node , visitor , context ) ; } } function transform ( sourceI ) { onNewSourceFile ( ) ; const source = sourceI as tse . SourceFile ; if ( source . isDeclarationFile ) { return source ; } onBeforeVisitNode ( source ) ; const newNode = ts . visitEachChild ( source , visitor , context ) ; newNode . symbol = source . symbol ; return newNode ; } return transform ; } export default function TransformerFactory ( program ) { return ( ctx ) => Transformer ( program , ctx ) ; }