2018-04-24 19:48:55 +02:00
/ * *
* Fake implementations of various compiler dependencies .
* /
2017-11-23 03:59:59 +01:00
namespace fakes {
2018-02-06 07:27:55 +01:00
const processExitSentinel = new Error ( "System exit" ) ;
2018-04-19 20:30:03 +02:00
export interface SystemOptions {
2017-11-22 04:47:13 +01:00
executingFilePath? : string ;
newLine ? : "\r\n" | "\n" ;
2018-04-19 20:30:03 +02:00
env? : Record < string , string > ;
2017-11-13 01:23:08 +01:00
}
2018-04-19 20:30:03 +02:00
/ * *
* A fake ` ts.System ` that leverages a virtual file system .
* /
export class System implements ts . System {
2018-01-20 06:58:46 +01:00
public readonly vfs : vfs.FileSystem ;
2018-04-19 20:30:03 +02:00
public readonly args : string [ ] = [ ] ;
public readonly output : string [ ] = [ ] ;
public readonly newLine : string ;
public readonly useCaseSensitiveFileNames : boolean ;
2018-05-22 23:46:57 +02:00
public exitCode : number | undefined ;
2017-11-22 04:47:13 +01:00
2018-04-19 20:30:03 +02:00
private readonly _executingFilePath : string | undefined ;
private readonly _env : Record < string , string > | undefined ;
2018-04-20 01:47:07 +02:00
constructor ( vfs : vfs.FileSystem , { executingFilePath , newLine = "\r\n" , env } : SystemOptions = { } ) {
2018-02-06 07:27:55 +01:00
this . vfs = vfs . isReadonly ? vfs . shadow ( ) : vfs ;
2018-01-20 06:58:46 +01:00
this . useCaseSensitiveFileNames = ! this . vfs . ignoreCase ;
2017-11-13 01:23:08 +01:00
this . newLine = newLine ;
this . _executingFilePath = executingFilePath ;
2018-04-19 20:30:03 +02:00
this . _env = env ;
2017-11-13 01:23:08 +01:00
}
public write ( message : string ) {
2018-04-19 20:30:03 +02:00
this . output . push ( message ) ;
2017-11-13 01:23:08 +01:00
}
public readFile ( path : string ) {
2018-04-19 20:30:03 +02:00
try {
const content = this . vfs . readFileSync ( path , "utf8" ) ;
2018-05-08 21:46:33 +02:00
return content === undefined ? undefined : utils . removeByteOrderMark ( content ) ;
2018-04-19 20:30:03 +02:00
}
catch {
return undefined ;
}
2017-11-13 01:23:08 +01:00
}
2017-11-22 04:47:13 +01:00
public writeFile ( path : string , data : string , writeByteOrderMark? : boolean ) : void {
2018-04-19 20:30:03 +02:00
this . vfs . mkdirpSync ( vpath . dirname ( path ) ) ;
2018-04-24 19:48:55 +02:00
this . vfs . writeFileSync ( path , writeByteOrderMark ? utils . addUTF8ByteOrderMark ( data ) : data ) ;
2017-11-13 01:23:08 +01:00
}
2018-05-24 19:59:07 +02:00
public deleteFile ( path : string ) {
this . vfs . unlinkSync ( path ) ;
}
2017-11-13 01:23:08 +01:00
public fileExists ( path : string ) {
2018-04-19 20:30:03 +02:00
const stats = this . _getStats ( path ) ;
return stats ? stats . isFile ( ) : false ;
2017-11-13 01:23:08 +01:00
}
public directoryExists ( path : string ) {
2018-04-19 20:30:03 +02:00
const stats = this . _getStats ( path ) ;
return stats ? stats . isDirectory ( ) : false ;
2017-11-13 01:23:08 +01:00
}
public createDirectory ( path : string ) : void {
2018-01-20 06:58:46 +01:00
this . vfs . mkdirpSync ( path ) ;
2017-11-13 01:23:08 +01:00
}
public getCurrentDirectory() {
2018-01-20 06:58:46 +01:00
return this . vfs . cwd ( ) ;
2017-11-13 01:23:08 +01:00
}
public getDirectories ( path : string ) {
2018-04-19 20:30:03 +02:00
const result : string [ ] = [ ] ;
try {
for ( const file of this . vfs . readdirSync ( path ) ) {
if ( this . vfs . statSync ( vpath . combine ( path , file ) ) . isDirectory ( ) ) {
result . push ( file ) ;
}
}
}
catch { /*ignore*/ }
return result ;
2017-11-13 01:23:08 +01:00
}
public readDirectory ( path : string , extensions? : ReadonlyArray < string > , exclude? : ReadonlyArray < string > , include? : ReadonlyArray < string > , depth? : number ) : string [ ] {
2018-04-20 01:47:07 +02:00
return ts . matchFiles ( path , extensions , exclude , include , this . useCaseSensitiveFileNames , this . getCurrentDirectory ( ) , depth , path = > this . getAccessibleFileSystemEntries ( path ) ) ;
}
public getAccessibleFileSystemEntries ( path : string ) : ts . FileSystemEntries {
const files : string [ ] = [ ] ;
const directories : string [ ] = [ ] ;
try {
for ( const file of this . vfs . readdirSync ( path ) ) {
try {
const stats = this . vfs . statSync ( vpath . combine ( path , file ) ) ;
if ( stats . isFile ( ) ) {
files . push ( file ) ;
}
else if ( stats . isDirectory ( ) ) {
directories . push ( file ) ;
2018-04-19 20:30:03 +02:00
}
}
2018-04-20 01:47:07 +02:00
catch { /*ignored*/ }
2018-04-19 20:30:03 +02:00
}
2018-04-20 01:47:07 +02:00
}
catch { /*ignored*/ }
return { files , directories } ;
2017-11-13 01:23:08 +01:00
}
public exit ( exitCode? : number ) {
this . exitCode = exitCode ;
2018-02-06 07:27:55 +01:00
throw processExitSentinel ;
2017-11-28 00:00:05 +01:00
}
2017-11-13 01:23:08 +01:00
public getFileSize ( path : string ) {
2018-04-19 20:30:03 +02:00
const stats = this . _getStats ( path ) ;
return stats && stats . isFile ( ) ? stats.size : 0 ;
2017-11-13 01:23:08 +01:00
}
public resolvePath ( path : string ) {
2018-01-20 06:58:46 +01:00
return vpath . resolve ( this . vfs . cwd ( ) , path ) ;
2017-11-13 01:23:08 +01:00
}
public getExecutingFilePath() {
2018-04-19 20:30:03 +02:00
if ( this . _executingFilePath === undefined ) return ts . notImplemented ( ) ;
2017-11-13 01:23:08 +01:00
return this . _executingFilePath ;
}
public getModifiedTime ( path : string ) {
2018-04-19 20:30:03 +02:00
const stats = this . _getStats ( path ) ;
2018-05-22 23:46:57 +02:00
return stats ? stats.mtime : undefined ! ; // TODO: GH#18217
2017-11-13 01:23:08 +01:00
}
2018-05-22 04:40:09 +02:00
public setModifiedTime ( path : string , time : Date ) {
this . vfs . utimesSync ( path , time , time ) ;
}
2017-11-13 01:23:08 +01:00
public createHash ( data : string ) : string {
2018-04-18 02:49:51 +02:00
return data ;
2017-11-13 01:23:08 +01:00
}
public realpath ( path : string ) {
2018-01-22 08:25:40 +01:00
try {
return this . vfs . realpathSync ( path ) ;
}
catch {
return path ;
}
2017-11-13 01:23:08 +01:00
}
2018-05-22 23:46:57 +02:00
public getEnvironmentVariable ( name : string ) : string {
return ( this . _env && this . _env [ name ] ) ! ; // TODO: GH#18217
2018-04-19 20:30:03 +02:00
}
private _getStats ( path : string ) {
try {
2018-05-12 05:44:21 +02:00
return this . vfs . existsSync ( path ) ? this . vfs . statSync ( path ) : undefined ;
2018-04-19 20:30:03 +02:00
}
catch {
return undefined ;
}
}
}
/ * *
* A fake ` ts.ParseConfigHost ` that leverages a virtual file system .
* /
export class ParseConfigHost implements ts . ParseConfigHost {
public readonly sys : System ;
constructor ( sys : System | vfs . FileSystem ) {
if ( sys instanceof vfs . FileSystem ) sys = new System ( sys ) ;
this . sys = sys ;
}
public get vfs() {
return this . sys . vfs ;
}
public get useCaseSensitiveFileNames() {
return this . sys . useCaseSensitiveFileNames ;
}
public fileExists ( fileName : string ) : boolean {
return this . sys . fileExists ( fileName ) ;
2017-11-13 01:23:08 +01:00
}
2018-04-19 20:30:03 +02:00
public directoryExists ( directoryName : string ) : boolean {
return this . sys . directoryExists ( directoryName ) ;
2017-11-13 01:23:08 +01:00
}
2017-12-18 21:12:51 +01:00
2018-04-19 20:30:03 +02:00
public readFile ( path : string ) : string | undefined {
return this . sys . readFile ( path ) ;
2017-12-18 21:12:51 +01:00
}
2017-11-13 01:23:08 +01:00
2018-04-19 20:30:03 +02:00
public readDirectory ( path : string , extensions : string [ ] , excludes : string [ ] , includes : string [ ] , depth : number ) : string [ ] {
return this . sys . readDirectory ( path , extensions , excludes , includes , depth ) ;
}
}
/ * *
* A fake ` ts.CompilerHost ` that leverages a virtual file system .
* /
2018-08-20 22:33:54 +02:00
export class CompilerHost implements ts . CompilerHost {
2018-04-19 20:30:03 +02:00
public readonly sys : System ;
public readonly defaultLibLocation : string ;
public readonly outputs : documents.TextDocument [ ] = [ ] ;
2018-05-08 21:46:33 +02:00
private readonly _outputsMap : collections.SortedMap < string , number > ;
2018-04-19 20:30:03 +02:00
public readonly traces : string [ ] = [ ] ;
public readonly shouldAssertInvariants = ! Harness . lightMode ;
private _setParentNodes : boolean ;
2018-04-24 19:48:55 +02:00
private _sourceFiles : collections.SortedMap < string , ts.SourceFile > ;
2018-11-17 01:02:23 +01:00
private _parseConfigHost : ParseConfigHost | undefined ;
2018-04-19 20:30:03 +02:00
private _newLine : string ;
2018-04-20 01:47:07 +02:00
constructor ( sys : System | vfs . FileSystem , options = ts . getDefaultCompilerOptions ( ) , setParentNodes = false ) {
if ( sys instanceof vfs . FileSystem ) sys = new System ( sys ) ;
2018-04-19 20:30:03 +02:00
this . sys = sys ;
this . defaultLibLocation = sys . vfs . meta . get ( "defaultLibLocation" ) || "" ;
this . _newLine = ts . getNewLineCharacter ( options , ( ) = > this . sys . newLine ) ;
2018-04-24 19:48:55 +02:00
this . _sourceFiles = new collections . SortedMap < string , ts.SourceFile > ( { comparer : sys.vfs.stringComparer , sort : "insertion" } ) ;
2018-04-19 20:30:03 +02:00
this . _setParentNodes = setParentNodes ;
2018-05-08 21:46:33 +02:00
this . _outputsMap = new collections . SortedMap ( this . vfs . stringComparer ) ;
2018-04-19 20:30:03 +02:00
}
public get vfs() {
return this . sys . vfs ;
}
public get parseConfigHost() {
return this . _parseConfigHost || ( this . _parseConfigHost = new ParseConfigHost ( this . sys ) ) ;
}
public getCurrentDirectory ( ) : string {
return this . sys . getCurrentDirectory ( ) ;
}
public useCaseSensitiveFileNames ( ) : boolean {
return this . sys . useCaseSensitiveFileNames ;
}
public getNewLine ( ) : string {
return this . _newLine ;
}
public getCanonicalFileName ( fileName : string ) : string {
return this . sys . useCaseSensitiveFileNames ? fileName : fileName.toLowerCase ( ) ;
2017-11-13 01:23:08 +01:00
}
2018-05-24 19:59:07 +02:00
public deleteFile ( fileName : string ) {
this . sys . deleteFile ( fileName ) ;
}
2018-04-19 20:30:03 +02:00
public fileExists ( fileName : string ) : boolean {
return this . sys . fileExists ( fileName ) ;
2017-11-13 01:23:08 +01:00
}
2018-04-19 20:30:03 +02:00
public directoryExists ( directoryName : string ) : boolean {
return this . sys . directoryExists ( directoryName ) ;
2017-11-13 01:23:08 +01:00
}
2018-05-22 04:40:09 +02:00
public getModifiedTime ( fileName : string ) {
return this . sys . getModifiedTime ( fileName ) ;
}
public setModifiedTime ( fileName : string , time : Date ) {
return this . sys . setModifiedTime ( fileName , time ) ;
}
2018-04-19 20:30:03 +02:00
public getDirectories ( path : string ) : string [ ] {
return this . sys . getDirectories ( path ) ;
}
2018-05-08 00:12:50 +02:00
public readDirectory ( path : string , extensions? : ReadonlyArray < string > , exclude? : ReadonlyArray < string > , include? : ReadonlyArray < string > , depth? : number ) : string [ ] {
return this . sys . readDirectory ( path , extensions , exclude , include , depth ) ;
}
2018-04-19 20:30:03 +02:00
public readFile ( path : string ) : string | undefined {
return this . sys . readFile ( path ) ;
}
public writeFile ( fileName : string , content : string , writeByteOrderMark : boolean ) {
2018-04-24 19:48:55 +02:00
if ( writeByteOrderMark ) content = utils . addUTF8ByteOrderMark ( content ) ;
2018-04-19 20:30:03 +02:00
this . sys . writeFile ( fileName , content ) ;
const document = new documents . TextDocument ( fileName , content ) ;
document . meta . set ( "fileName" , fileName ) ;
this . vfs . filemeta ( fileName ) . set ( "document" , document ) ;
2018-05-08 21:46:33 +02:00
if ( ! this . _outputsMap . has ( document . file ) ) {
this . _outputsMap . set ( document . file , this . outputs . length ) ;
2018-04-19 20:30:03 +02:00
this . outputs . push ( document ) ;
}
2018-05-22 23:46:57 +02:00
this . outputs [ this . _outputsMap . get ( document . file ) ! ] = document ;
2018-04-19 20:30:03 +02:00
}
public trace ( s : string ) : void {
this . traces . push ( s ) ;
}
public realpath ( path : string ) : string {
return this . sys . realpath ( path ) ;
}
public getDefaultLibLocation ( ) : string {
return vpath . resolve ( this . getCurrentDirectory ( ) , this . defaultLibLocation ) ;
}
public getDefaultLibFileName ( options : ts.CompilerOptions ) : string {
2018-05-03 23:00:52 +02:00
return vpath . resolve ( this . getDefaultLibLocation ( ) , ts . getDefaultLibFileName ( options ) ) ;
2018-04-19 20:30:03 +02:00
}
public getSourceFile ( fileName : string , languageVersion : number ) : ts . SourceFile | undefined {
const canonicalFileName = this . getCanonicalFileName ( vpath . resolve ( this . getCurrentDirectory ( ) , fileName ) ) ;
const existing = this . _sourceFiles . get ( canonicalFileName ) ;
if ( existing ) return existing ;
const content = this . readFile ( canonicalFileName ) ;
if ( content === undefined ) return undefined ;
// A virtual file system may shadow another existing virtual file system. This
// allows us to reuse a common virtual file system structure across multiple
// tests. If a virtual file is a shadow, it is likely that the file will be
// reused across multiple tests. In that case, we cache the SourceFile we parse
// so that it can be reused across multiple tests to avoid the cost of
// repeatedly parsing the same file over and over (such as lib.d.ts).
const cacheKey = this . vfs . shadowRoot && ` SourceFile[languageVersion= ${ languageVersion } ,setParentNodes= ${ this . _setParentNodes } ] ` ;
if ( cacheKey ) {
const meta = this . vfs . filemeta ( canonicalFileName ) ;
const sourceFileFromMetadata = meta . get ( cacheKey ) as ts . SourceFile | undefined ;
2018-05-25 01:21:06 +02:00
if ( sourceFileFromMetadata && sourceFileFromMetadata . getFullText ( ) === content ) {
2018-04-19 20:30:03 +02:00
this . _sourceFiles . set ( canonicalFileName , sourceFileFromMetadata ) ;
return sourceFileFromMetadata ;
}
}
const parsed = ts . createSourceFile ( fileName , content , languageVersion , this . _setParentNodes || this . shouldAssertInvariants ) ;
if ( this . shouldAssertInvariants ) {
Utils . assertInvariants ( parsed , /*parent*/ undefined ) ;
}
this . _sourceFiles . set ( canonicalFileName , parsed ) ;
if ( cacheKey ) {
// store the cached source file on the unshadowed file with the same version.
const stats = this . vfs . statSync ( canonicalFileName ) ;
let fs = this . vfs ;
while ( fs . shadowRoot ) {
try {
2018-05-22 23:46:57 +02:00
const shadowRootStats = fs . shadowRoot . existsSync ( canonicalFileName ) ? fs . shadowRoot . statSync ( canonicalFileName ) : undefined ! ; // TODO: GH#18217
2018-04-19 20:30:03 +02:00
if ( shadowRootStats . dev !== stats . dev ||
shadowRootStats . ino !== stats . ino ||
shadowRootStats . mtimeMs !== stats . mtimeMs ) {
break ;
}
fs = fs . shadowRoot ;
}
catch {
break ;
}
}
if ( fs !== this . vfs ) {
fs . filemeta ( canonicalFileName ) . set ( cacheKey , parsed ) ;
}
}
return parsed ;
2017-11-13 01:23:08 +01:00
}
2017-11-28 02:01:15 +01:00
}
2018-08-20 22:33:54 +02:00
2018-12-19 01:12:37 +01:00
export class SolutionBuilderHost extends CompilerHost implements ts . SolutionBuilderHost < ts.BuilderProgram > {
createProgram = ts . createAbstractBuilder ;
2018-08-20 22:33:54 +02:00
diagnostics : ts.Diagnostic [ ] = [ ] ;
reportDiagnostic ( diagnostic : ts.Diagnostic ) {
this . diagnostics . push ( diagnostic ) ;
}
reportSolutionBuilderStatus ( diagnostic : ts.Diagnostic ) {
this . diagnostics . push ( diagnostic ) ;
}
clearDiagnostics() {
this . diagnostics . length = 0 ;
}
assertDiagnosticMessages ( . . . expected : ts.DiagnosticMessage [ ] ) {
const actual = this . diagnostics . slice ( ) ;
if ( actual . length !== expected . length ) {
assert . fail < any > ( actual , expected , ` Diagnostic arrays did not match - got \ r \ n ${ actual . map ( a = > " " + a . messageText ) . join ( "\r\n" ) } \ r \ nexpected \ r \ n ${ expected . map ( e = > " " + e . message ) . join ( "\r\n" ) } ` ) ;
}
for ( let i = 0 ; i < actual . length ; i ++ ) {
if ( actual [ i ] . code !== expected [ i ] . code ) {
assert . fail ( actual [ i ] . messageText , expected [ i ] . message , ` Mismatched error code - expected diagnostic ${ i } " ${ actual [ i ] . messageText } " to match ${ expected [ i ] . message } ` ) ;
}
}
}
printDiagnostics ( header = "== Diagnostics ==" ) {
const out = ts . createDiagnosticReporter ( ts . sys ) ;
ts . sys . write ( header + "\r\n" ) ;
for ( const d of this . diagnostics ) {
out ( d ) ;
}
}
}
2018-02-06 07:27:55 +01:00
}
2017-11-28 02:01:15 +01:00