diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ecddd721211..48a1dcc73e7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1073,9 +1073,16 @@ namespace ts { // - This calls resolveModuleNames, and then calls findSourceFile for each resolved module. // As all these operations happen - and are nested - within the createProgram call, they close over the below variables. // The current resolution depth is tracked by incrementing/decrementing as the depth first search progresses. - const maxNodeModulesJsDepth = options.maxNodeModuleJsDepth || 2; + const maxNodeModulesJsDepth = typeof options.maxNodeModuleJsDepth === "number" ? options.maxNodeModuleJsDepth : 2; let currentNodeModulesJsDepth = 0; + // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track + // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. + const modulesWithElidedImports: Map = {}; + + // Track source files that are JavaScript files found by searching under node_modules, as these shouldn't be compiled. + const jsFilesFoundSearchingNodeModules: Map = {}; + const start = new Date().getTime(); host = host || createCompilerHost(options); @@ -1230,6 +1237,7 @@ namespace ts { (oldOptions.rootDir !== options.rootDir) || (oldOptions.configFilePath !== options.configFilePath) || (oldOptions.baseUrl !== options.baseUrl) || + (oldOptions.maxNodeModuleJsDepth !== options.maxNodeModuleJsDepth) || !arrayIsEqualTo(oldOptions.typeRoots, oldOptions.typeRoots) || !arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) || !mapIsEqualTo(oldOptions.paths, options.paths)) { @@ -1353,7 +1361,10 @@ namespace ts { getNewLine: () => host.getNewLine(), getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, - getSourceFiles: program.getSourceFiles, + getSourceFiles: () => filter(program.getSourceFiles(), + // Remove JavaScript files found by searching node_modules from the source files to emit + sourceFile => !lookUp(jsFilesFoundSearchingNodeModules, sourceFile.path) + ), writeFile: writeFileCallback || ( (fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)), isEmitBlocked, @@ -1888,6 +1899,14 @@ namespace ts { reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd); } + // See if we need to reprocess the imports due to prior skipped imports + if (file && lookUp(modulesWithElidedImports, file.path)) { + if (currentNodeModulesJsDepth < maxNodeModulesJsDepth) { // TODO: Check for off-by-ones + modulesWithElidedImports[file.path] = false; + processImportedModules(file, getDirectoryPath(fileName)); + } + } + return file; } @@ -2026,6 +2045,8 @@ namespace ts { for (let i = 0; i < moduleNames.length; i++) { const resolution = resolutions[i]; setResolvedModule(file, moduleNames[i], resolution); + const resolvedPath = resolution ? toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName) : undefined; + // add file to program only if: // - resolution was successful // - noResolve is falsy @@ -2033,20 +2054,27 @@ namespace ts { // - it's not a top level JavaScript module that exceeded the search max const isJsFileUnderNodeModules = resolution && resolution.isExternalLibraryImport && hasJavaScriptFileExtension(resolution.resolvedFileName); + if (isJsFileUnderNodeModules) { + jsFilesFoundSearchingNodeModules[resolvedPath] = true; currentNodeModulesJsDepth++; } - const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && - !(isJsFileUnderNodeModules && currentNodeModulesJsDepth > maxNodeModulesJsDepth); - if (shouldAddFile) { + const elideImport = isJsFileUnderNodeModules && currentNodeModulesJsDepth > maxNodeModulesJsDepth; + const shouldAddFile = resolution && !options.noResolve && i < file.imports.length && !elideImport; + + if (elideImport) { + modulesWithElidedImports[file.path] = true; + } + else if (shouldAddFile) { findSourceFile(resolution.resolvedFileName, - toPath(resolution.resolvedFileName, currentDirectory, getCanonicalFileName), + resolvedPath, /*isDefaultLib*/ false, /*isReference*/ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end); } + if (isJsFileUnderNodeModules) { currentNodeModulesJsDepth--; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d01678ac767..c7fbeec6a63 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2275,7 +2275,7 @@ namespace ts { else { const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile]; for (const sourceFile of sourceFiles) { - // Don't emit if source file is a declaration file, or TODO: was found by a search under 'node_modules' + // Don't emit if source file is a declaration file if (!isDeclarationFile(sourceFile)) { onSingleFileEmit(host, sourceFile); } @@ -2310,7 +2310,6 @@ namespace ts { function onBundledEmit(host: EmitHost) { // Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified const bundledSources = filter(host.getSourceFiles(), - // TODO: Don't emit from source resolved by searching under node_modules sourceFile => !isDeclarationFile(sourceFile) && // Not a declaration file (!isExternalModule(sourceFile) || // non module file !!getEmitModuleKind(options))); // module that can emit - note falsy value from getEmitModuleKind means the module kind that shouldn't be emitted