Skip to content

Commit 9bf965a

Browse files
Merge pull request #20516 from RyanCavanaugh/port20464
Dedupe local types from ATA and reuse old programs correctly
2 parents 7790c96 + cb7c6cc commit 9bf965a

File tree

5 files changed

+82
-13
lines changed

5 files changed

+82
-13
lines changed

src/compiler/program.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ namespace ts {
712712

713713
interface OldProgramState {
714714
program: Program | undefined;
715-
file: SourceFile;
715+
oldSourceFile: SourceFile | undefined;
716716
/** The collection of paths modified *since* the old program. */
717717
modifiedFilePaths: Path[];
718718
}
@@ -762,7 +762,6 @@ namespace ts {
762762
/** A transient placeholder used to mark predicted resolution in the result list. */
763763
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = <any>{};
764764

765-
766765
for (let i = 0; i < moduleNames.length; i++) {
767766
const moduleName = moduleNames[i];
768767
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
@@ -833,9 +832,13 @@ namespace ts {
833832
// If we change our policy of rechecking failed lookups on each program create,
834833
// we should adjust the value returned here.
835834
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean {
836-
const resolutionToFile = getResolvedModule(oldProgramState.file, moduleName);
837-
if (resolutionToFile) {
838-
// module used to be resolved to file - ignore it
835+
const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile, moduleName);
836+
const resolvedFile = resolutionToFile && oldProgramState.program && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName);
837+
if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) {
838+
// In the old program, we resolved to an ambient module that was in the same
839+
// place as we expected to find an actual module file.
840+
// We actually need to return 'false' here even though this seems like a 'true' case
841+
// because the normal module resolution algorithm will find this anyway.
839842
return false;
840843
}
841844
const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName);
@@ -1009,7 +1012,7 @@ namespace ts {
10091012
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
10101013
if (resolveModuleNamesWorker) {
10111014
const moduleNames = getModuleNames(newSourceFile);
1012-
const oldProgramState = { program: oldProgram, file: oldSourceFile, modifiedFilePaths };
1015+
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile, modifiedFilePaths };
10131016
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile, oldProgramState);
10141017
// ensure that module resolution results are still correct
10151018
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
@@ -1952,7 +1955,7 @@ namespace ts {
19521955
if (file.imports.length || file.moduleAugmentations.length) {
19531956
// Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
19541957
const moduleNames = getModuleNames(file);
1955-
const oldProgramState = { program: oldProgram, file, modifiedFilePaths };
1958+
const oldProgramState: OldProgramState = { program: oldProgram, oldSourceFile: oldProgram && oldProgram.getSourceFile(file.fileName), modifiedFilePaths };
19561959
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory), file, oldProgramState);
19571960
Debug.assert(resolutions.length === moduleNames.length);
19581961
for (let i = 0; i < moduleNames.length; i++) {

src/harness/unittests/reuseProgramStructure.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,19 @@ namespace ts {
389389
checkResolvedModulesCache(program_4, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts"), "c": undefined }));
390390
});
391391

392+
it("set the resolvedImports after re-using an ambient external module declaration", () => {
393+
const files = [
394+
{ name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";') },
395+
{ name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },
396+
];
397+
const options: CompilerOptions = { target, typeRoots: ["/types"] };
398+
const program1 = newProgram(files, ["/a.ts"], options);
399+
const program2 = updateProgram(program1, ["/a.ts"], options, files => {
400+
files[0].text = files[0].text.updateProgram('import * as aa from "a";');
401+
});
402+
assert.isDefined(program2.getSourceFile("/a.ts").resolvedModules.get("a"), "'a' is not an unresolved module after re-use");
403+
});
404+
392405
it("resolved type directives cache follows type directives", () => {
393406
const files = [
394407
{ name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") },

src/harness/unittests/typingsInstaller.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,37 @@ namespace ts.projectSystem {
228228
projectService.checkNumberOfProjects({ externalProjects: 1 });
229229
});
230230

231+
it("external project - deduplicate from local @types packages", () => {
232+
const appJs = {
233+
path: "/a/b/app.js",
234+
content: ""
235+
};
236+
const nodeDts = {
237+
path: "/node_modules/@types/node/index.d.ts",
238+
content: "declare var node;"
239+
};
240+
const host = createServerHost([appJs, nodeDts]);
241+
const installer = new (class extends Installer {
242+
constructor() {
243+
super(host, { typesRegistry: createTypesRegistry("node") });
244+
}
245+
installWorker() {
246+
assert(false, "nothing should get installed");
247+
}
248+
})();
249+
250+
const projectFileName = "/a/app/test.csproj";
251+
const projectService = createProjectService(host, { typingsInstaller: installer });
252+
projectService.openExternalProject({
253+
projectFileName,
254+
options: {},
255+
rootFiles: [toExternalFile(appJs.path)],
256+
typeAcquisition: { enable: true, include: ["node"] }
257+
});
258+
installer.checkPendingCommands(/*expectedCount*/ 0);
259+
projectService.checkNumberOfProjects({ externalProjects: 1 });
260+
});
261+
231262
it("external project - no auto in typing acquisition, no .d.ts/js files", () => {
232263
const file1 = {
233264
path: "/a/b/app.ts",

src/server/project.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,14 @@ namespace ts.server {
508508
}
509509
abstract getTypeAcquisition(): TypeAcquisition;
510510

511+
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition {
512+
if (!newTypeAcquisition || !newTypeAcquisition.include) {
513+
// Nothing to filter out, so just return as-is
514+
return newTypeAcquisition;
515+
}
516+
return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
517+
}
518+
511519
getExternalFiles(): SortedReadonlyArray<string> {
512520
return emptyArray as SortedReadonlyArray<string>;
513521
}
@@ -720,7 +728,8 @@ namespace ts.server {
720728
this.projectStateVersion++;
721729
}
722730

723-
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>) {
731+
/* @internal */
732+
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>, ambientModules: string[]) {
724733
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
725734
if (cached) {
726735
// found cached result - use it and return
@@ -733,7 +742,7 @@ namespace ts.server {
733742
if (file.resolvedModules) {
734743
file.resolvedModules.forEach((resolvedModule, name) => {
735744
// pick unresolved non-relative names
736-
if (!resolvedModule && !isExternalModuleNameRelative(name)) {
745+
if (!resolvedModule && !isExternalModuleNameRelative(name) && !isAmbientlyDeclaredModule(name)) {
737746
// for non-scoped names extract part up-to the first slash
738747
// for scoped names - extract up to the second slash
739748
let trimmed = name.trim();
@@ -750,6 +759,10 @@ namespace ts.server {
750759
});
751760
}
752761
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
762+
763+
function isAmbientlyDeclaredModule(name: string) {
764+
return ambientModules.some(m => m === name);
765+
}
753766
}
754767

755768
/**
@@ -779,8 +792,9 @@ namespace ts.server {
779792
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
780793
if (hasChanges || changedFiles.length) {
781794
const result: string[] = [];
795+
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
782796
for (const sourceFile of this.program.getSourceFiles()) {
783-
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
797+
this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
784798
}
785799
this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result);
786800
}
@@ -806,6 +820,13 @@ namespace ts.server {
806820
return !hasChanges;
807821
}
808822

823+
824+
825+
protected removeExistingTypings(include: string[]): string[] {
826+
const existing = ts.getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost);
827+
return include.filter(i => existing.indexOf(i) < 0);
828+
}
829+
809830
private setTypings(typings: SortedReadonlyArray<string>): boolean {
810831
if (arrayIsEqualTo(this.typingFiles, typings)) {
811832
return false;
@@ -1299,7 +1320,7 @@ namespace ts.server {
12991320
}
13001321

13011322
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
1302-
this.typeAcquisition = newTypeAcquisition;
1323+
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
13031324
}
13041325

13051326
getTypeAcquisition() {
@@ -1445,7 +1466,7 @@ namespace ts.server {
14451466
Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined");
14461467
Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined");
14471468
Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined");
1448-
this.typeAcquisition = newTypeAcquisition;
1469+
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
14491470
}
14501471
}
14511472
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7251,6 +7251,7 @@ declare namespace ts.server {
72517251
disableLanguageService(): void;
72527252
getProjectName(): string;
72537253
abstract getTypeAcquisition(): TypeAcquisition;
7254+
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition;
72547255
getExternalFiles(): SortedReadonlyArray<string>;
72557256
getSourceFile(path: Path): SourceFile;
72567257
close(): void;
@@ -7271,12 +7272,12 @@ declare namespace ts.server {
72717272
removeFile(info: ScriptInfo, fileExists: boolean, detachFromProject: boolean): void;
72727273
registerFileUpdate(fileName: string): void;
72737274
markAsDirty(): void;
7274-
private extractUnresolvedImportsFromSourceFile(file, result);
72757275
/**
72767276
* Updates set of files that contribute to this project
72777277
* @returns: true if set of files in the project stays the same and false - otherwise.
72787278
*/
72797279
updateGraph(): boolean;
7280+
protected removeExistingTypings(include: string[]): string[];
72807281
private setTypings(typings);
72817282
private updateGraphWorker();
72827283
private detachScriptInfoFromProject(uncheckedFileName);

0 commit comments

Comments
 (0)