diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf0c048ecbfb8..006b87cb542ee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -107,7 +107,12 @@ namespace ts { getJsxElementAttributesType, getJsxIntrinsicTagNames, - isOptionalParameter + isOptionalParameter, + tryFindAmbientModuleWithoutAugmentations: moduleName => { + // we deliberately exclude augmentations + // since we are only interested in declarations of the module itself + return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); + } }; const tupleTypes: GenericType[] = []; @@ -1370,16 +1375,11 @@ namespace ts { return; } - const isRelative = isExternalModuleNameRelative(moduleName); - const quotedName = '"' + moduleName + '"'; - if (!isRelative) { - const symbol = getSymbol(globals, quotedName, SymbolFlags.ValueModule); - if (symbol) { - // merged symbol is module declaration symbol combined with all augmentations - return getMergedSymbol(symbol); - } + const ambientModule = tryFindAmbientModule(moduleName, /*withAugmentations*/ true); + if (ambientModule) { + return ambientModule; } - + const isRelative = isExternalModuleNameRelative(moduleName); const resolvedModule = getResolvedModule(getSourceFileOfNode(location), moduleReference); const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); @@ -4734,6 +4734,15 @@ namespace ts { } } + function tryFindAmbientModule(moduleName: string, withAugmentations: boolean) { + if (isExternalModuleNameRelative(moduleName)) { + return undefined; + } + const symbol = getSymbol(globals, `"${moduleName}"`, SymbolFlags.ValueModule); + // merged symbol is module declaration symbol combined with all augmentations + return symbol && withAugmentations ? getMergedSymbol(symbol) : symbol; + } + function isOptionalParameter(node: ParameterDeclaration) { if (hasQuestionToken(node) || isJSDocOptionalParameter(node)) { return true; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7f88d5fed72bc..5290782c156a2 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2889,6 +2889,14 @@ "category": "Error", "code": 6143 }, + "Module '{0}' was resolved as locally declared ambient module in file '{1}'.": { + "category": "Message", + "code": 6144 + }, + "Module '{0}' was resolved as ambient module declared in '{1}' since this file was not modified.": { + "category": "Message", + "code": 6145 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 9e159a356c7c7..86e0157d84729 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -2,12 +2,15 @@ /// namespace ts { - function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; - function trace(host: ModuleResolutionHost): void { + + /* @internal */ + export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void; + export function trace(host: ModuleResolutionHost): void { host.trace(formatMessage.apply(undefined, arguments)); } - function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { + /* @internal */ + export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleResolutionHost): boolean { return compilerOptions.traceResolution && host.trace !== undefined; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 726df380e81b1..82d274e0487df 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -462,6 +462,130 @@ namespace ts { return classifiableNames; } + interface OldProgramState { + program: Program; + file: SourceFile; + modifiedFilePaths: Path[]; + } + + function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile, oldProgramState?: OldProgramState) { + if (!oldProgramState && !file.ambientModuleNames.length) { + // if old program state is not supplied and file does not contain locally defined ambient modules + // then the best we can do is fallback to the default logic + return resolveModuleNamesWorker(moduleNames, containingFile); + } + + // at this point we know that either + // - file has local declarations for ambient modules + // OR + // - old program state is available + // OR + // - both of items above + // With this it is possible that we can tell how some module names from the initial list will be resolved + // without doing actual resolution (in particular if some name was resolved to ambient module). + // Such names should be excluded from the list of module names that will be provided to `resolveModuleNamesWorker` + // since we don't want to resolve them again. + + // this is a list of modules for which we cannot predict resolution so they should be actually resolved + let unknownModuleNames: string[]; + // this is a list of combined results assembles from predicted and resolved results. + // Order in this list matches the order in the original list of module names `moduleNames` which is important + // so later we can split results to resolutions of modules and resolutions of module augmentations. + let result: ResolvedModuleFull[]; + // a transient placeholder that is used to mark predicted resolution in the result list + const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {}; + + for (let i = 0; i < moduleNames.length; i++) { + const moduleName = moduleNames[i]; + // module name is known to be resolved to ambient module if + // - module name is contained in the list of ambient modules that are locally declared in the file + // - in the old program module name was resolved to ambient module whose declaration is in non-modified file + // (so the same module declaration will land in the new program) + let isKnownToResolveToAmbientModule = false; + if (contains(file.ambientModuleNames, moduleName)) { + isKnownToResolveToAmbientModule = true; + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile); + } + } + else { + isKnownToResolveToAmbientModule = checkModuleNameResolvedToAmbientModuleInNonModifiedFile(moduleName, oldProgramState); + } + + if (isKnownToResolveToAmbientModule) { + if (!unknownModuleNames) { + // found a first module name for which result can be prediced + // this means that this module name should not be passed to `resolveModuleNamesWorker`. + // We'll use a separate list for module names that are definitely unknown. + result = new Array(moduleNames.length); + // copy all module names that appear before the current one in the list + // since they are known to be unknown + unknownModuleNames = moduleNames.slice(0, i); + } + // mark prediced resolution in the result list + result[i] = predictedToResolveToAmbientModuleMarker; + } + else if (unknownModuleNames) { + // found unknown module name and we are already using separate list for those - add it to the list + unknownModuleNames.push(moduleName); + } + } + + if (!unknownModuleNames) { + // we've looked throught the list but have not seen any predicted resolution + // use default logic + return resolveModuleNamesWorker(moduleNames, containingFile); + } + + const resolutions = unknownModuleNames.length + ? resolveModuleNamesWorker(unknownModuleNames, containingFile) + : emptyArray; + + // combine results of resolutions and predicted results + let j = 0; + for (let i = 0; i < result.length; i++) { + if (result[i] == predictedToResolveToAmbientModuleMarker) { + result[i] = undefined; + } + else { + result[i] = resolutions[j]; + j++; + } + } + Debug.assert(j === resolutions.length); + return result; + + function checkModuleNameResolvedToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState?: OldProgramState): boolean { + if (!oldProgramState) { + return false; + } + const resolutionToFile = getResolvedModule(oldProgramState.file, moduleName); + if (resolutionToFile) { + // module used to be resolved to file - ignore it + return false; + } + const ambientModule = oldProgram.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName); + if (!(ambientModule && ambientModule.declarations)) { + return false; + } + + // at least one of declarations should come from non-modified source file + const firstUnmodifiedFile = forEach(ambientModule.declarations, d => { + const f = getSourceFileOfNode(d); + return !contains(oldProgramState.modifiedFilePaths, f.path) && f; + }); + + if (!firstUnmodifiedFile) { + return false; + } + + if (isTraceEnabled(options, host)) { + trace(host, Diagnostics.Module_0_was_resolved_as_ambient_module_declared_in_1_since_this_file_was_not_modified, moduleName, firstUnmodifiedFile.fileName); + } + return true; + } + } + function tryReuseStructureFromOldProgram(): boolean { if (!oldProgram) { return false; @@ -489,7 +613,7 @@ namespace ts { // check if program source files has changed in the way that can affect structure of the program const newSourceFiles: SourceFile[] = []; const filePaths: Path[] = []; - const modifiedSourceFiles: SourceFile[] = []; + const modifiedSourceFiles: { oldFile: SourceFile, newFile: SourceFile }[] = []; for (const oldSourceFile of oldProgram.getSourceFiles()) { let newSourceFile = host.getSourceFileByPath @@ -532,29 +656,8 @@ namespace ts { return false; } - const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory); - if (resolveModuleNamesWorker) { - const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral); - const resolutions = resolveModuleNamesWorker(moduleNames, newSourceFilePath); - // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); - if (resolutionsChanged) { - return false; - } - } - if (resolveTypeReferenceDirectiveNamesWorker) { - const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, x => x.fileName); - const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath); - // ensure that types resolutions are still correct - const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); - if (resolutionsChanged) { - return false; - } - } - // pass the cache of module/types resolutions from the old source file - newSourceFile.resolvedModules = oldSourceFile.resolvedModules; - newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; - modifiedSourceFiles.push(newSourceFile); + // tentatively approve the file + modifiedSourceFiles.push({ oldFile: oldSourceFile, newFile: newSourceFile }); } else { // file has no changes - use it as is @@ -565,6 +668,33 @@ namespace ts { newSourceFiles.push(newSourceFile); } + const modifiedFilePaths = modifiedSourceFiles.map(f => f.newFile.path); + // try to verify results of module resolution + for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) { + const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory); + if (resolveModuleNamesWorker) { + const moduleNames = map(concatenate(newSourceFile.imports, newSourceFile.moduleAugmentations), getTextOfLiteral); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, { file: oldSourceFile, program: oldProgram, modifiedFilePaths }); + // ensure that module resolution results are still correct + const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); + if (resolutionsChanged) { + return false; + } + } + if (resolveTypeReferenceDirectiveNamesWorker) { + const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, x => x.fileName); + const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath); + // ensure that types resolutions are still correct + const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); + if (resolutionsChanged) { + return false; + } + } + // pass the cache of module/types resolutions from the old source file + newSourceFile.resolvedModules = oldSourceFile.resolvedModules; + newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; + } + // update fileName -> file mapping for (let i = 0, len = newSourceFiles.length; i < len; i++) { filesByName.set(filePaths[i], newSourceFiles[i]); @@ -574,7 +704,7 @@ namespace ts { fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics(); for (const modifiedFile of modifiedSourceFiles) { - fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile); + fileProcessingDiagnostics.reattachFileDiagnostics(modifiedFile.newFile); } resolvedTypeReferenceDirectives = oldProgram.getResolvedTypeReferenceDirectives(); oldProgram.structureIsReused = true; @@ -994,9 +1124,11 @@ namespace ts { const isJavaScriptFile = isSourceFileJavaScript(file); const isExternalModuleFile = isExternalModule(file); + const isDtsFile = isDeclarationFile(file); let imports: LiteralExpression[]; let moduleAugmentations: LiteralExpression[]; + let ambientModules: string[]; // If we are importing helpers, we need to add a synthetic reference to resolve the // helpers library. @@ -1018,6 +1150,7 @@ namespace ts { file.imports = imports || emptyArray; file.moduleAugmentations = moduleAugmentations || emptyArray; + file.ambientModuleNames = ambientModules || emptyArray; return; @@ -1053,6 +1186,10 @@ namespace ts { (moduleAugmentations || (moduleAugmentations = [])).push(moduleName); } else if (!inAmbientModule) { + if (isDtsFile) { + // for global .d.ts files record name of ambient module + (ambientModules || (ambientModules = [])).push(moduleName.text); + } // An AmbientExternalModuleDeclaration declares an external module. // This type of declaration is permitted only in the global module. // The StringLiteral must specify a top - level external module name. @@ -1298,7 +1435,7 @@ namespace ts { if (file.imports.length || file.moduleAugmentations.length) { file.resolvedModules = createMap(); const moduleNames = map(concatenate(file.imports, file.moduleAugmentations), getTextOfLiteral); - const resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory)); + const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file); Debug.assert(resolutions.length === moduleNames.length); for (let i = 0; i < moduleNames.length; i++) { const resolution = resolutions[i]; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 381289b133b49..e4c92c502a456 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2102,6 +2102,7 @@ namespace ts { /* @internal */ imports: LiteralExpression[]; /* @internal */ moduleAugmentations: LiteralExpression[]; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; + /* @internal */ ambientModuleNames: string[]; // The synthesized identifier for an imported external helpers module. /* @internal */ externalHelpersModuleName?: Identifier; } @@ -2296,6 +2297,8 @@ namespace ts { isOptionalParameter(node: ParameterDeclaration): boolean; getAmbientModules(): Symbol[]; + /* @internal */ tryFindAmbientModuleWithoutAugmentations(moduleName: string): Symbol; + // Should not be called directly. Should only be accessed through the Program instance. /* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[]; /* @internal */ getGlobalDiagnostics(): Diagnostic[]; diff --git a/src/harness/unittests/moduleResolution.ts b/src/harness/unittests/moduleResolution.ts index 104d9f9e394eb..0e391445ebaff 100644 --- a/src/harness/unittests/moduleResolution.ts +++ b/src/harness/unittests/moduleResolution.ts @@ -1065,5 +1065,69 @@ import b = require("./moduleB"); assert.equal(diagnostics2.length, 1, "expected one diagnostic"); assert.equal(diagnostics1[0].messageText, diagnostics2[0].messageText, "expected one diagnostic"); }); + + it ("Modules in the same .d.ts file are preferred to external files", () => { + const f = { + name: "/a/b/c/c/app.d.ts", + content: ` + declare module "fs" { + export interface Stat { id: number } + } + declare module "fs-client" { + import { Stat } from "fs"; + export function foo(): Stat; + }` + }; + const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); + const compilerHost: CompilerHost = { + fileExists : fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames() { + assert(false, "resolveModuleNames should not be called"); + return undefined; + } + }; + createProgram([f.name], {}, compilerHost); + }); + + it ("Modules in .ts file are not checked in the same file", () => { + const f = { + name: "/a/b/c/c/app.ts", + content: ` + declare module "fs" { + export interface Stat { id: number } + } + declare module "fs-client" { + import { Stat } from "fs"; + export function foo(): Stat; + }` + }; + const file = createSourceFile(f.name, f.content, ScriptTarget.ES2015); + const compilerHost: CompilerHost = { + fileExists : fileName => fileName === file.fileName, + getSourceFile: fileName => fileName === file.fileName ? file : undefined, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: notImplemented, + getCurrentDirectory: () => "/", + getDirectories: () => [], + getCanonicalFileName: f => f.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + readFile: fileName => fileName === file.fileName ? file.text : undefined, + resolveModuleNames(moduleNames: string[], _containingFile: string) { + assert.deepEqual(moduleNames, ["fs"]); + return [undefined]; + } + }; + createProgram([f.name], {}, compilerHost); + }); }); } diff --git a/src/harness/unittests/reuseProgramStructure.ts b/src/harness/unittests/reuseProgramStructure.ts index 215e05658a964..386e02e24507a 100644 --- a/src/harness/unittests/reuseProgramStructure.ts +++ b/src/harness/unittests/reuseProgramStructure.ts @@ -22,6 +22,11 @@ namespace ts { interface ProgramWithSourceTexts extends Program { sourceTexts?: NamedSourceText[]; + host: TestCompilerHost; + } + + interface TestCompilerHost extends CompilerHost { + getTrace(): string[]; } class SourceText implements IScriptSnapshot { @@ -101,10 +106,21 @@ namespace ts { return file; } - function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget): CompilerHost { - const files = arrayToMap(texts, t => t.name, t => createSourceFileWithText(t.name, t.text, target)); + function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost { + const files = arrayToMap(texts, t => t.name, t => { + if (oldProgram) { + const oldFile = oldProgram.getSourceFile(t.name); + if (oldFile && oldFile.sourceText.getVersion() === t.text.getVersion()) { + return oldFile; + } + } + return createSourceFileWithText(t.name, t.text, target); + }); + const trace: string[] = []; return { + trace: s => trace.push(s), + getTrace: () => trace, getSourceFile(fileName): SourceFile { return files[fileName]; }, @@ -130,23 +146,25 @@ namespace ts { fileExists: fileName => fileName in files, readFile: fileName => { return fileName in files ? files[fileName].text : undefined; - } + }, }; } - function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): Program { + function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts { const host = createTestCompilerHost(texts, options.target); const program = createProgram(rootNames, options, host); program.sourceTexts = texts; + program.host = host; return program; } - function updateProgram(oldProgram: Program, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void) { + function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void) { const texts: NamedSourceText[] = (oldProgram).sourceTexts.slice(0); updater(texts); - const host = createTestCompilerHost(texts, options.target); + const host = createTestCompilerHost(texts, options.target, oldProgram); const program = createProgram(rootNames, options, host, oldProgram); program.sourceTexts = texts; + program.host = host; return program; } @@ -355,6 +373,112 @@ namespace ts { assert.isTrue(!program_3.structureIsReused); checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMap({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); }); + + it("can reuse ambient module declarations from non-modified files", () => { + const files = [ + { name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") }, + { name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") } + ]; + const options = { target: ScriptTarget.ES2015, traceResolution: true }; + const program = newProgram(files, files.map(f => f.name), options); + assert.deepEqual(program.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs.ts' does not exist.", + "File '/a/b/node_modules/@types/fs.tsx' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs/index.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.tsx' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs.ts' does not exist.", + "File '/a/node_modules/@types/fs.tsx' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs/index.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.tsx' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs.ts' does not exist.", + "File '/node_modules/@types/fs.tsx' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs/index.ts' does not exist.", + "File '/node_modules/@types/fs/index.tsx' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs'"); + + const program_2 = updateProgram(program, program.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var x = 1;"); + }); + assert.deepEqual(program_2.host.getTrace(), [ + "Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified." + ], "should reuse 'fs' since node.d.ts was not changed"); + + const program_3 = updateProgram(program_2, program_2.getRootFileNames(), options, f => { + f[0].text = f[0].text.updateProgram("var y = 1;"); + f[1].text = f[1].text.updateProgram("declare var process: any"); + }); + assert.deepEqual(program_3.host.getTrace(), + [ + "======== Resolving module 'fs' from '/a/b/app.ts'. ========", + "Module resolution kind is not specified, using 'Classic'.", + "File '/a/b/fs.ts' does not exist.", + "File '/a/b/fs.tsx' does not exist.", + "File '/a/b/fs.d.ts' does not exist.", + "File '/a/fs.ts' does not exist.", + "File '/a/fs.tsx' does not exist.", + "File '/a/fs.d.ts' does not exist.", + "File '/fs.ts' does not exist.", + "File '/fs.tsx' does not exist.", + "File '/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs.ts' does not exist.", + "File '/a/b/node_modules/@types/fs.tsx' does not exist.", + "File '/a/b/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/package.json' does not exist.", + "File '/a/b/node_modules/@types/fs/index.ts' does not exist.", + "File '/a/b/node_modules/@types/fs/index.tsx' does not exist.", + "File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/node_modules/@types/fs.ts' does not exist.", + "File '/a/node_modules/@types/fs.tsx' does not exist.", + "File '/a/node_modules/@types/fs.d.ts' does not exist.", + "File '/a/node_modules/@types/fs/package.json' does not exist.", + "File '/a/node_modules/@types/fs/index.ts' does not exist.", + "File '/a/node_modules/@types/fs/index.tsx' does not exist.", + "File '/a/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/node_modules/@types/fs.ts' does not exist.", + "File '/node_modules/@types/fs.tsx' does not exist.", + "File '/node_modules/@types/fs.d.ts' does not exist.", + "File '/node_modules/@types/fs/package.json' does not exist.", + "File '/node_modules/@types/fs/index.ts' does not exist.", + "File '/node_modules/@types/fs/index.tsx' does not exist.", + "File '/node_modules/@types/fs/index.d.ts' does not exist.", + "File '/a/b/fs.js' does not exist.", + "File '/a/b/fs.jsx' does not exist.", + "File '/a/fs.js' does not exist.", + "File '/a/fs.jsx' does not exist.", + "File '/fs.js' does not exist.", + "File '/fs.jsx' does not exist.", + "======== Module name 'fs' was not resolved. ========", + ], "should look for 'fs' again since node.d.ts was changed"); + + }); }); describe("host is optional", () => { diff --git a/src/services/services.ts b/src/services/services.ts index 8c11c707e5d60..8abcf8f76846c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -472,6 +472,7 @@ namespace ts { public imports: LiteralExpression[]; public moduleAugmentations: LiteralExpression[]; private namedDeclarations: Map; + public ambientModuleNames: string[]; constructor(kind: SyntaxKind, pos: number, end: number) { super(kind, pos, end);