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);