mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-21 13:14:43 -06:00
module resolution: prefer locally defined ambient modules, reuse resolutions to ambient modules from the old program (#11999)
module resolution: prefer locally defined ambient modules, reuse resolutions to ambient modules from the old program
This commit is contained in:
parent
7b34b612be
commit
ab75ea75d3
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,12 +2,15 @@
|
||||
/// <reference path="diagnosticInformationMap.generated.ts" />
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 = <any>{};
|
||||
|
||||
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<ResolvedModuleFull>();
|
||||
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];
|
||||
|
||||
@ -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[];
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 = <SourceFileWithText>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 = <ProgramWithSourceTexts>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[] = (<ProgramWithSourceTexts>oldProgram).sourceTexts.slice(0);
|
||||
updater(texts);
|
||||
const host = createTestCompilerHost(texts, options.target);
|
||||
const host = createTestCompilerHost(texts, options.target, oldProgram);
|
||||
const program = <ProgramWithSourceTexts>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", () => {
|
||||
|
||||
@ -472,6 +472,7 @@ namespace ts {
|
||||
public imports: LiteralExpression[];
|
||||
public moduleAugmentations: LiteralExpression[];
|
||||
private namedDeclarations: Map<Declaration[]>;
|
||||
public ambientModuleNames: string[];
|
||||
|
||||
constructor(kind: SyntaxKind, pos: number, end: number) {
|
||||
super(kind, pos, end);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user