Merge pull request #20464 from RyanCavanaugh/fix20402
Don't add ambiently declared modules to ATA's unresolvedModules list
This commit is contained in:
Коммит
ee283d1524
|
@ -704,7 +704,7 @@ namespace ts {
|
|||
|
||||
interface OldProgramState {
|
||||
program: Program | undefined;
|
||||
file: SourceFile;
|
||||
oldSourceFile: SourceFile | undefined;
|
||||
/** The collection of paths modified *since* the old program. */
|
||||
modifiedFilePaths: Path[];
|
||||
}
|
||||
|
@ -754,7 +754,6 @@ namespace ts {
|
|||
/** A transient placeholder 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];
|
||||
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
|
||||
|
@ -825,9 +824,13 @@ namespace ts {
|
|||
// If we change our policy of rechecking failed lookups on each program create,
|
||||
// we should adjust the value returned here.
|
||||
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean {
|
||||
const resolutionToFile = getResolvedModule(oldProgramState.file, moduleName);
|
||||
if (resolutionToFile) {
|
||||
// module used to be resolved to file - ignore it
|
||||
const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile, moduleName);
|
||||
const resolvedFile = resolutionToFile && oldProgramState.program && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName);
|
||||
if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) {
|
||||
// In the old program, we resolved to an ambient module that was in the same
|
||||
// place as we expected to find an actual module file.
|
||||
// We actually need to return 'false' here even though this seems like a 'true' case
|
||||
// because the normal module resolution algorithm will find this anyway.
|
||||
return false;
|
||||
}
|
||||
const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName);
|
||||
|
@ -1001,7 +1004,7 @@ namespace ts {
|
|||
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
|
||||
if (resolveModuleNamesWorker) {
|
||||
const moduleNames = getModuleNames(newSourceFile);
|
||||
const oldProgramState = { program: oldProgram, file: oldSourceFile, modifiedFilePaths };
|
||||
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths };
|
||||
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState);
|
||||
// ensure that module resolution results are still correct
|
||||
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
|
||||
|
@ -1945,7 +1948,7 @@ namespace ts {
|
|||
if (file.imports.length || file.moduleAugmentations.length) {
|
||||
// Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
|
||||
const moduleNames = getModuleNames(file);
|
||||
const oldProgramState = { program: oldProgram, file, modifiedFilePaths };
|
||||
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile: oldProgram && oldProgram.getSourceFile(file.fileName), modifiedFilePaths };
|
||||
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState);
|
||||
Debug.assert(resolutions.length === moduleNames.length);
|
||||
for (let i = 0; i < moduleNames.length; i++) {
|
||||
|
|
|
@ -389,6 +389,19 @@ namespace ts {
|
|||
checkResolvedModulesCache(program4, "a.ts", createMapFromTemplate({ b: createResolvedModule("b.ts"), c: undefined }));
|
||||
});
|
||||
|
||||
it("set the resolvedImports after re-using an ambient external module declaration", () => {
|
||||
const files = [
|
||||
{ name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') },
|
||||
{ name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },
|
||||
];
|
||||
const options: CompilerOptions = { target, typeRoots: ["/types"] };
|
||||
const program1 = newProgram(files, ["/a.ts"], options);
|
||||
const program2 = updateProgram(program1, ["/a.ts"], options, files => {
|
||||
files[0].text = files[0].text.updateProgram('import * as aa from "a";');
|
||||
});
|
||||
assert.isDefined(program2.getSourceFile("/a.ts").resolvedModules.get("a"), "'a' is not an unresolved module after re-use");
|
||||
});
|
||||
|
||||
it("resolved type directives cache follows type directives", () => {
|
||||
const files = [
|
||||
{ name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") },
|
||||
|
|
|
@ -228,6 +228,37 @@ namespace ts.projectSystem {
|
|||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
|
||||
it("external project - deduplicate from local @types packages", () => {
|
||||
const appJs = {
|
||||
path: "/a/b/app.js",
|
||||
content: ""
|
||||
};
|
||||
const nodeDts = {
|
||||
path: "/node_modules/@types/node/index.d.ts",
|
||||
content: "declare var node;"
|
||||
};
|
||||
const host = createServerHost([appJs, nodeDts]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { typesRegistry: createTypesRegistry("node") });
|
||||
}
|
||||
installWorker() {
|
||||
assert(false, "nothing should get installed");
|
||||
}
|
||||
})();
|
||||
|
||||
const projectFileName = "/a/app/test.csproj";
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openExternalProject({
|
||||
projectFileName,
|
||||
options: {},
|
||||
rootFiles: [toExternalFile(appJs.path)],
|
||||
typeAcquisition: { enable: true, include: ["node"] }
|
||||
});
|
||||
installer.checkPendingCommands(/*expectedCount*/ 0);
|
||||
projectService.checkNumberOfProjects({ externalProjects: 1 });
|
||||
});
|
||||
|
||||
it("external project - no auto in typing acquisition, no .d.ts/js files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/app.ts",
|
||||
|
|
|
@ -506,6 +506,14 @@ namespace ts.server {
|
|||
}
|
||||
abstract getTypeAcquisition(): TypeAcquisition;
|
||||
|
||||
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition {
|
||||
if (!newTypeAcquisition || !newTypeAcquisition.include) {
|
||||
// Nothing to filter out, so just return as-is
|
||||
return newTypeAcquisition;
|
||||
}
|
||||
return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
|
||||
}
|
||||
|
||||
getExternalFiles(): SortedReadonlyArray<string> {
|
||||
return emptyArray as SortedReadonlyArray<string>;
|
||||
}
|
||||
|
@ -718,7 +726,8 @@ namespace ts.server {
|
|||
this.projectStateVersion++;
|
||||
}
|
||||
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>) {
|
||||
/* @internal */
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>, ambientModules: string[]) {
|
||||
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
|
||||
if (cached) {
|
||||
// found cached result - use it and return
|
||||
|
@ -731,7 +740,7 @@ namespace ts.server {
|
|||
if (file.resolvedModules) {
|
||||
file.resolvedModules.forEach((resolvedModule, name) => {
|
||||
// pick unresolved non-relative names
|
||||
if (!resolvedModule && !isExternalModuleNameRelative(name)) {
|
||||
if (!resolvedModule && !isExternalModuleNameRelative(name) && !isAmbientlyDeclaredModule(name)) {
|
||||
// for non-scoped names extract part up-to the first slash
|
||||
// for scoped names - extract up to the second slash
|
||||
let trimmed = name.trim();
|
||||
|
@ -748,6 +757,10 @@ namespace ts.server {
|
|||
});
|
||||
}
|
||||
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
||||
|
||||
function isAmbientlyDeclaredModule(name: string) {
|
||||
return ambientModules.some(m => m === name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -777,8 +790,9 @@ namespace ts.server {
|
|||
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
|
||||
if (hasChanges || changedFiles.length) {
|
||||
const result: string[] = [];
|
||||
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result);
|
||||
}
|
||||
|
@ -804,6 +818,13 @@ namespace ts.server {
|
|||
return !hasChanges;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected removeExistingTypings(include: string[]): string[] {
|
||||
const existing = ts.getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost);
|
||||
return include.filter(i => existing.indexOf(i) < 0);
|
||||
}
|
||||
|
||||
private setTypings(typings: SortedReadonlyArray<string>): boolean {
|
||||
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
||||
return false;
|
||||
|
@ -1299,7 +1320,7 @@ namespace ts.server {
|
|||
}
|
||||
|
||||
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
|
||||
this.typeAcquisition = newTypeAcquisition;
|
||||
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
|
||||
}
|
||||
|
||||
getTypeAcquisition() {
|
||||
|
@ -1445,7 +1466,7 @@ namespace ts.server {
|
|||
Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined");
|
||||
Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined");
|
||||
Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined");
|
||||
this.typeAcquisition = newTypeAcquisition;
|
||||
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7295,6 +7295,7 @@ declare namespace ts.server {
|
|||
disableLanguageService(): void;
|
||||
getProjectName(): string;
|
||||
abstract getTypeAcquisition(): TypeAcquisition;
|
||||
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition;
|
||||
getExternalFiles(): SortedReadonlyArray<string>;
|
||||
getSourceFile(path: Path): SourceFile;
|
||||
close(): void;
|
||||
|
@ -7315,12 +7316,12 @@ declare namespace ts.server {
|
|||
removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean): void;
|
||||
registerFileUpdate(fileName: string): void;
|
||||
markAsDirty(): void;
|
||||
private extractUnresolvedImportsFromSourceFile(file, result);
|
||||
/**
|
||||
* Updates set of files that contribute to this project
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
*/
|
||||
updateGraph(): boolean;
|
||||
protected removeExistingTypings(include: string[]): string[];
|
||||
private setTypings(typings);
|
||||
private updateGraphWorker();
|
||||
private detachScriptInfoFromProject(uncheckedFileName);
|
||||
|
|
Загрузка…
Ссылка в новой задаче