import * as upath from 'upath' ; import * as fse from 'fs-extra' ; import { createHash } from 'crypto' ; import * as TypeScript from 'typescript' ; import glob = require ( 'fast-glob' ) ; import { VueTypeScriptParser } from './VueTypeScriptParser' ; import { Shout } from './Shout' ; interface CachedSource { filePath : string ; source : TypeScript . SourceFile ; version : string ; } /**
 * An in-memory cache of TypeScript source files.
 */ export class TypeScriptSourceStore { /**
     * Gets the cached source.
     */ private readonly sources : Map < string , CachedSource > = new Map < string , CachedSource > ( ) ; /**
     * Gets the parser language target.
     */ target : TypeScript . ScriptTarget ; /**
     * Gets the Vue SFC TypeScript parser.
     */ tsVueParser : VueTypeScriptParser | undefined ; constructor ( target , tsVueParser ) { this . target = target ; this . tsVueParser = tsVueParser ; } get sourcePaths ( ) : string [ ] { return Array . from ( this . sources . keys ( ) ) ; } param sourcePath getFilePath ( sourcePath ) { const s = this . sources . get ( sourcePath ) ; if ( s ) { return s . filePath ; } else { return sourcePath ; } } private readonly _typeCheckGlobs : string [ ] = [ ] ; get typeCheckGlobs ( ) : string [ ] { return this . _typeCheckGlobs ; } async loadFolder ( folder ) { const tsGlobs = upath . join ( folder , '**' , '*.ts' ) ; const tsxGlobs = upath . join ( folder , '**' , '*.tsx' ) ; const vueGlobs = upath . join ( folder , '**' , '*.vue' ) ; this . _typeCheckGlobs . push ( tsGlobs , tsxGlobs , vueGlobs ) ; const files = await glob ( this . _typeCheckGlobs ) ; const readSources = [ ] ; for ( const file of files ) { readSources . push ( this . loadFile ( file ) ) ; } await Promise . all ( readSources ) ; } param filePath async loadFile ( filePath ) { const raw = await fse . readFile ( filePath , 'utf8' ) ; return this . parseThenStoreSource ( filePath , raw ) ; } param filePath loadFileSync ( filePath ) { const raw = fse . readFileSync ( filePath , 'utf8' ) ; return this . parseThenStoreSource ( filePath , raw ) ; } param content private calculateFileVersion ( content ) { const hash = createHash ( 'sha256' ) ; hash . update ( content ) ; return hash . digest ( 'hex' ) ; } param filePath param sourceCode private parseThenStoreSource ( filePath , sourceCode ) { filePath = upath . toUnix ( filePath ) ; let sourcePath = filePath ; if ( filePath . endsWith ( '.vue' ) ) { sourcePath = upath . addExt ( filePath , '.ts' ) ; if ( this . tsVueParser ) { sourceCode = this . tsVueParser . parse ( sourceCode ) ; } else { Shout . warning ( 'Vue-TypeScript parser is not loaded!' ) ; } } const version = this . calculateFileVersion ( sourceCode ) ; const previousSource = this . sources . get ( sourcePath ) ; if ( previousSource && previousSource . version === version ) { return false ; } this . sources . set ( sourcePath , { filePath : filePath , source : TypeScript . createSourceFile ( sourcePath , sourceCode , this . target ) , version : version } ) ; return true ; } param sourcePath getSource ( sourcePath ) { const s1 = this . sources . get ( sourcePath ) ; if ( s1 ) { return s1 . source ; } const filePath = this . getFilePath ( sourcePath ) ; this . loadFileSync ( filePath ) ; const s2 = this . sources . get ( sourcePath ) ; if ( ! s2 ) { throw new Error ( `Source ${ sourcePath } should not be undefined!` ) ; } return s2 . source ; } param realFilePath removeFile ( filePath ) { filePath = upath . toUnix ( filePath ) ; let sourcePath = filePath ; if ( filePath . endsWith ( '.vue' ) ) { sourcePath = upath . addExt ( filePath , '.ts' ) ; } return this . sources . delete ( sourcePath ) ; } }