namespace Harness { export interface TypeWriterTypeResult { line : number ; syntaxKind : number ; sourceText : string ; type : string ; } export interface TypeWriterSymbolResult { line : number ; syntaxKind : number ; sourceText : string ; symbol : string ; } export interface TypeWriterResult { line : number ; syntaxKind : number ; sourceText : string ; symbol ? : string ; type ? : string ; } function * forEachASTNode ( node ) { const work = [ node ] ; while ( work . length ) { const elem = work . pop ( ) ! ; yield elem ; const resChildren = [ ] ; ts . forEachChild ( elem , c => { resChildren . unshift ( c ) ; } ) ; work . push ( ... resChildren ) ; } } export class TypeWriterWalker { currentSourceFile ! : ts . SourceFile ; private checker : ts . TypeChecker ; constructor ( private program , fullTypeCheck , private hadErrorBaseline ) { this . checker = fullTypeCheck ? program . getDiagnosticsProducingTypeChecker ( ) : program . getTypeChecker ( ) ; } public * getSymbols ( fileName ) { const sourceFile = this . program . getSourceFile ( fileName ) ! ; this . currentSourceFile = sourceFile ; const gen = this . visitNode ( sourceFile , true ) ; for ( let { done , value } = gen . next ( ) ; ! done ; { done , value } = gen . next ( ) ) { yield value as TypeWriterSymbolResult ; } } public * getTypes ( fileName ) { const sourceFile = this . program . getSourceFile ( fileName ) ! ; this . currentSourceFile = sourceFile ; const gen = this . visitNode ( sourceFile , false ) ; for ( let { done , value } = gen . next ( ) ; ! done ; { done , value } = gen . next ( ) ) { yield value as TypeWriterTypeResult ; } } private * visitNode ( node , isSymbolWalk ) { const gen = forEachASTNode ( node ) ; let res = gen . next ( ) ; for ( ; ! res . done ; res = gen . next ( ) ) { const { value : node } = res ; if ( ts . isExpressionNode ( node ) || node . kind === ts . SyntaxKind . Identifier || ts . isDeclarationName ( node ) ) { const result = this . writeTypeOrSymbol ( node , isSymbolWalk ) ; if ( result ) { yield result ; } } } } private isImportStatementName ( node ) { if ( ts . isImportSpecifier ( node . parent ) && ( node . parent . name === node || node . parent . propertyName === node ) ) return true ; if ( ts . isImportClause ( node . parent ) && node . parent . name === node ) return true ; if ( ts . isImportEqualsDeclaration ( node . parent ) && node . parent . name === node ) return true ; return false ; } private isExportStatementName ( node ) { if ( ts . isExportAssignment ( node . parent ) && node . parent . expression === node ) return true ; if ( ts . isExportSpecifier ( node . parent ) && ( node . parent . name === node || node . parent . propertyName === node ) ) return true ; return false ; } private isIntrinsicJsxTag ( node ) { const p = node . parent ; if ( ! ( ts . isJsxOpeningElement ( p ) || ts . isJsxClosingElement ( p ) || ts . isJsxSelfClosingElement ( p ) ) ) return false ; if ( p . tagName !== node ) return false ; return ts . isIntrinsicJsxName ( node . getText ( ) ) ; } private writeTypeOrSymbol ( node , isSymbolWalk ) { const actualPos = ts . skipTrivia ( this . currentSourceFile . text , node . pos ) ; const lineAndCharacter = this . currentSourceFile . getLineAndCharacterOfPosition ( actualPos ) ; const sourceText = ts . getSourceTextOfNodeFromSourceFile ( this . currentSourceFile , node ) ; if ( ! isSymbolWalk ) { if ( ts . isPartOfTypeNode ( node ) || ts . isIdentifier ( node ) && ! ( ts . getMeaningFromDeclaration ( node . parent ) & ts . SemanticMeaning . Value ) && ! ( ts . isTypeAliasDeclaration ( node . parent ) && node . parent . name === node ) ) { return undefined ; } let type = ts . isExpressionWithTypeArgumentsInClassExtendsClause ( node . parent ) ? this . checker . getTypeAtLocation ( node . parent ) : undefined ; if ( ! type || type . flags & ts . TypeFlags . Any ) type = this . checker . getTypeAtLocation ( node ) ; const typeString = ( ! this . hadErrorBaseline && type . flags & ts . TypeFlags . Any && ! ts . isBindingElement ( node . parent ) && ! ts . isPropertyAccessOrQualifiedName ( node . parent ) && ! ts . isLabelName ( node ) && ! ( ts . isModuleDeclaration ( node . parent ) && ts . isGlobalScopeAugmentation ( node . parent ) ) && ! ts . isMetaProperty ( node . parent ) && ! this . isImportStatementName ( node ) && ! this . isExportStatementName ( node ) && ! this . isIntrinsicJsxTag ( node ) ) ? ( type as ts . IntrinsicType ) . intrinsicName : this . checker . typeToString ( type , node . parent , ts . TypeFormatFlags . NoTruncation | ts . TypeFormatFlags . AllowUniqueESSymbolType ) ; return { line : lineAndCharacter . line , syntaxKind : node . kind , sourceText , type : typeString } ; } const symbol = this . checker . getSymbolAtLocation ( node ) ; if ( ! symbol ) { return ; } let symbolString = "Symbol(" + this . checker . symbolToString ( symbol , node . parent ) ; if ( symbol . declarations ) { let count = 0 ; for ( const declaration of symbol . declarations ) { if ( count >= 5 ) { symbolString += ` ... and ${ symbol . declarations . length - count } more` ; break ; } count ++ ; symbolString += ", " ; if ( ( declaration as any ) . __symbolTestOutputCache ) { symbolString += ( declaration as any ) . __symbolTestOutputCache ; continue ; } const declSourceFile = declaration . getSourceFile ( ) ; const declLineAndCharacter = declSourceFile . getLineAndCharacterOfPosition ( declaration . pos ) ; const fileName = ts . getBaseFileName ( declSourceFile . fileName ) ; const isLibFile = /lib(.*)\.d\.ts/i . test ( fileName ) ; const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter . line }, ${ isLibFile ? "--" : declLineAndCharacter . character })` ; symbolString += declText ; ( declaration as any ) . __symbolTestOutputCache = declText ; } } symbolString += ")" ; return { line : lineAndCharacter . line , syntaxKind : node . kind , sourceText , symbol : symbolString } ; } } }