@@ -132,15 +132,72 @@ export class CompilerState {
132132 private static _createCompilerHost ( commandLine : ts . ParsedCommandLine ,
133133 options : IExtractorInvokeOptions | undefined ) : ts . CompilerHost {
134134
135- // Create a default CompilerHost that we can override
135+ // Create a default CompilerHost that we will override
136136 const compilerHost : ts . CompilerHost = ts . createCompilerHost ( commandLine . options ) ;
137137
138+ // Save a copy of the original members. Note that "compilerHost" cannot be the copy, because
139+ // createCompilerHost() captures that instance in a closure that is used by the members.
140+ const defaultCompilerHost : ts . CompilerHost = { ...compilerHost } ;
141+
138142 if ( options && options . typescriptCompilerFolder ) {
139143 // Prevent a closure parameter
140144 const typescriptCompilerLibFolder : string = path . join ( options . typescriptCompilerFolder , 'lib' ) ;
141145 compilerHost . getDefaultLibLocation = ( ) => typescriptCompilerLibFolder ;
142146 }
143147
148+ // Used by compilerHost.fileExists()
149+ // .d.ts file path --> whether the file exists
150+ const dtsExistsCache : Map < string , boolean > = new Map < string , boolean > ( ) ;
151+
152+ // Used by compilerHost.fileExists()
153+ // Example: "c:/folder/file.part.ts"
154+ const fileExtensionRegExp : RegExp = / ^ ( .+ ) ( \. [ a - z 0 - 9 _ ] + ) $ / i;
155+
156+ compilerHost . fileExists = ( fileName : string ) : boolean => {
157+ // In certain deprecated setups, the compiler may write its output files (.js and .d.ts)
158+ // in the same folder as the corresponding input file (.ts or .tsx). When following imports,
159+ // API Extractor wants to analyze the .d.ts file; however recent versions of the compiler engine
160+ // will instead choose the .ts file. To work around this, we hook fileExists() to hide the
161+ // existence of those files.
162+
163+ // Is "fileName" a .d.ts file? The double extension ".d.ts" needs to be matched specially.
164+ if ( ! ExtractorConfig . hasDtsFileExtension ( fileName ) ) {
165+ // It's not a .d.ts file. Is the file extension a potential source file?
166+ const match : RegExpExecArray | null = fileExtensionRegExp . exec ( fileName ) ;
167+ if ( match ) {
168+ // Example: "c:/folder/file.part"
169+ const pathWithoutExtension : string = match [ 1 ] ;
170+ // Example: ".ts"
171+ const fileExtension : string = match [ 2 ] ;
172+
173+ switch ( fileExtension . toLocaleLowerCase ( ) ) {
174+ case '.ts' :
175+ case '.tsx' :
176+ case '.js' :
177+ case '.jsx' :
178+ // Yes, this is a possible source file. Is there a corresponding .d.ts file in the same folder?
179+ const dtsFileName : string = `${ pathWithoutExtension } .d.ts` ;
180+
181+ let dtsFileExists : boolean | undefined = dtsExistsCache . get ( dtsFileName ) ;
182+ if ( dtsFileExists === undefined ) {
183+ dtsFileExists = defaultCompilerHost . fileExists ! ( dtsFileName ) ;
184+ dtsExistsCache . set ( dtsFileName , dtsFileExists ) ;
185+ }
186+
187+ if ( dtsFileExists ) {
188+ // fileName is a potential source file and a corresponding .d.ts file exists.
189+ // Thus, API Extractor should ignore this file (so the .d.ts file will get analyzed instead).
190+ return false ;
191+ }
192+ break ;
193+ }
194+ }
195+ }
196+
197+ // Fall through to the default implementation
198+ return defaultCompilerHost . fileExists ! ( fileName ) ;
199+ } ;
200+
144201 return compilerHost ;
145202 }
146203}
0 commit comments