@@ -37,8 +37,9 @@ import * as readline from "readline";
3737import * as ts from "./typescript" ;
3838import * as ast_extractor from "./ast_extractor" ;
3939
40- import { Project } from "./common" ;
40+ import { Project , PackageLocationMap } from "./common" ;
4141import { TypeTable } from "./type_table" ;
42+ import { VirtualSourceRoot } from "./virtual_source_root" ;
4243
4344interface ParseCommand {
4445 command : "parse" ;
@@ -47,7 +48,8 @@ interface ParseCommand {
4748interface OpenProjectCommand {
4849 command : "open-project" ;
4950 tsConfig : string ;
50- packageLocations : [ string , string ] [ ] ;
51+ packageEntryPoints : [ string , string ] [ ] ;
52+ packageJsonFiles : [ string , string ] [ ] ;
5153}
5254interface CloseProjectCommand {
5355 command : "close-project" ;
@@ -243,20 +245,62 @@ function parseSingleFile(filename: string): {ast: ts.SourceFile, code: string} {
243245 return { ast, code} ;
244246}
245247
248+ const nodeModulesRex = / [ / \\ ] n o d e _ m o d u l e s [ / \\ ] ( (?: @ [ \w . - ] + [ / \\ ] ) ? \w [ \w . - ] * ) [ / \\ ] ( .* ) / ;
249+
246250function handleOpenProjectCommand ( command : OpenProjectCommand ) {
247251 Error . stackTraceLimit = Infinity ;
248252 let tsConfigFilename = String ( command . tsConfig ) ;
249253 let tsConfig = ts . readConfigFile ( tsConfigFilename , ts . sys . readFile ) ;
250254 let basePath = pathlib . dirname ( tsConfigFilename ) ;
251255
256+ let packageEntryPoints = new Map ( command . packageEntryPoints ) ;
257+ let packageJsonFiles = new Map ( command . packageJsonFiles ) ;
258+ let virtualSourceRoot = new VirtualSourceRoot ( process . cwd ( ) , process . env [ "CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR" ] ) ;
259+
260+ /**
261+ * Rewrites path segments of form `node_modules/PACK/suffix` to be relative to
262+ * the location of package PACK in the source tree, if it exists.
263+ */
264+ function redirectNodeModulesPath ( path : string ) {
265+ let nodeModulesMatch = nodeModulesRex . exec ( path ) ;
266+ if ( nodeModulesMatch == null ) return null ;
267+ let packageName = nodeModulesMatch [ 1 ] ;
268+ let packageJsonFile = packageJsonFiles . get ( packageName ) ;
269+ if ( packageJsonFile == null ) return null ;
270+ let packageDir = pathlib . dirname ( packageJsonFile ) ;
271+ let suffix = nodeModulesMatch [ 2 ] ;
272+ let finalPath = pathlib . join ( packageDir , suffix ) ;
273+ if ( ! ts . sys . fileExists ( finalPath ) ) return null ;
274+ return finalPath ;
275+ }
276+
277+ /**
278+ * Create the host passed to the tsconfig.json parser.
279+ *
280+ * We override its file system access in case there is an "extends"
281+ * clause pointing into "./node_modules", which must be redirected to
282+ * the location of an installed package or a checked-in package.
283+ */
252284 let parseConfigHost : ts . ParseConfigHost = {
253285 useCaseSensitiveFileNames : true ,
254- readDirectory : ts . sys . readDirectory ,
255- fileExists : ( path : string ) => fs . existsSync ( path ) ,
256- readFile : ts . sys . readFile ,
286+ readDirectory : ts . sys . readDirectory , // No need to override traversal/glob matching
287+ fileExists : ( path : string ) => {
288+ return ts . sys . fileExists ( path )
289+ || virtualSourceRoot . toVirtualPathIfFileExists ( path ) != null
290+ || redirectNodeModulesPath ( path ) != null ;
291+ } ,
292+ readFile : ( path : string ) => {
293+ if ( ! fs . existsSync ( path ) ) {
294+ let virtualPath = virtualSourceRoot . toVirtualPathIfFileExists ( path ) ;
295+ if ( virtualPath != null ) return ts . sys . readFile ( virtualPath ) ;
296+ virtualPath = redirectNodeModulesPath ( path ) ;
297+ if ( virtualPath != null ) return ts . sys . readFile ( virtualPath ) ;
298+ }
299+ return ts . sys . readFile ( path ) ;
300+ }
257301 } ;
258302 let config = ts . parseJsonConfigFileContent ( tsConfig . config , parseConfigHost , basePath ) ;
259- let project = new Project ( tsConfigFilename , config , state . typeTable , new Map ( command . packageLocations ) ) ;
303+ let project = new Project ( tsConfigFilename , config , state . typeTable , packageEntryPoints , virtualSourceRoot ) ;
260304 project . load ( ) ;
261305
262306 state . project = project ;
@@ -530,7 +574,8 @@ if (process.argv.length > 2) {
530574 handleOpenProjectCommand ( {
531575 command : "open-project" ,
532576 tsConfig : argument ,
533- packageLocations : [ ] ,
577+ packageEntryPoints : [ ] ,
578+ packageJsonFiles : [ ] ,
534579 } ) ;
535580 for ( let sf of state . project . program . getSourceFiles ( ) ) {
536581 if ( pathlib . basename ( sf . fileName ) === "lib.d.ts" ) continue ;
0 commit comments