Skip to content

Commit d2647a1

Browse files
authored
Merge pull request #27483 from Microsoft/redirects
Fix issue of program not being reused when host implements getSourceFileByPath
2 parents 8feddcd + dd3277c commit d2647a1

File tree

2 files changed

+68
-65
lines changed

2 files changed

+68
-65
lines changed

src/compiler/program.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,10 +2024,12 @@ namespace ts {
20242024
}
20252025
}
20262026

2027-
function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path): SourceFile {
2027+
function createRedirectSourceFile(redirectTarget: SourceFile, unredirected: SourceFile, fileName: string, path: Path, resolvedPath: Path, originalFileName: string): SourceFile {
20282028
const redirect: SourceFile = Object.create(redirectTarget);
20292029
redirect.fileName = fileName;
20302030
redirect.path = path;
2031+
redirect.resolvedPath = resolvedPath;
2032+
redirect.originalFileName = originalFileName;
20312033
redirect.redirectInfo = { redirectTarget, unredirected };
20322034
Object.defineProperties(redirect, {
20332035
id: {
@@ -2118,7 +2120,7 @@ namespace ts {
21182120
if (fileFromPackageId) {
21192121
// Some other SourceFile already exists with this package name and version.
21202122
// Instead of creating a duplicate, just redirect to the existing one.
2121-
const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path); // TODO: GH#18217
2123+
const dupFile = createRedirectSourceFile(fileFromPackageId, file!, fileName, path, toPath(fileName), originalFileName); // TODO: GH#18217
21222124
redirectTargetsMap.add(fileFromPackageId.path, fileName);
21232125
filesByName.set(path, dupFile);
21242126
sourceFileToPackageName.set(path, packageId.name);

src/testRunner/unittests/reuseProgramStructure.ts

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ namespace ts {
104104
return file;
105105
}
106106

107-
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
107+
export function createTestCompilerHost(texts: ReadonlyArray<NamedSourceText>, target: ScriptTarget, oldProgram?: ProgramWithSourceTexts, useGetSourceFileByPath?: boolean) {
108108
const files = arrayToMap(texts, t => t.name, t => {
109109
if (oldProgram) {
110110
let oldFile = <SourceFileWithText>oldProgram.getSourceFile(t.name);
@@ -117,55 +117,47 @@ namespace ts {
117117
}
118118
return createSourceFileWithText(t.name, t.text, target);
119119
});
120+
const useCaseSensitiveFileNames = sys && sys.useCaseSensitiveFileNames;
121+
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
120122
const trace: string[] = [];
121-
122-
return {
123+
const result: TestCompilerHost = {
123124
trace: s => trace.push(s),
124125
getTrace: () => trace,
125-
getSourceFile(fileName): SourceFile {
126-
return files.get(fileName)!;
127-
},
128-
getDefaultLibFileName(): string {
129-
return "lib.d.ts";
130-
},
126+
getSourceFile: fileName => files.get(fileName),
127+
getDefaultLibFileName: () => "lib.d.ts",
131128
writeFile: notImplemented,
132-
getCurrentDirectory(): string {
133-
return "";
134-
},
135-
getDirectories(): string[] {
136-
return [];
137-
},
138-
getCanonicalFileName(fileName): string {
139-
return sys && sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
140-
},
141-
useCaseSensitiveFileNames(): boolean {
142-
return sys && sys.useCaseSensitiveFileNames;
143-
},
144-
getNewLine(): string {
145-
return sys ? sys.newLine : newLine;
146-
},
129+
getCurrentDirectory: () => "",
130+
getDirectories: () => [],
131+
getCanonicalFileName,
132+
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
133+
getNewLine: () => sys ? sys.newLine : newLine,
147134
fileExists: fileName => files.has(fileName),
148135
readFile: fileName => {
149136
const file = files.get(fileName);
150137
return file && file.text;
151138
},
152139
};
140+
if (useGetSourceFileByPath) {
141+
const filesByPath = mapEntries(files, (fileName, file) => [toPath(fileName, "", getCanonicalFileName), file]);
142+
result.getSourceFileByPath = (_fileName, path) => filesByPath.get(path);
143+
}
144+
return result;
153145
}
154146

155-
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
156-
const host = createTestCompilerHost(texts, options.target!);
147+
export function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions, useGetSourceFileByPath?: boolean): ProgramWithSourceTexts {
148+
const host = createTestCompilerHost(texts, options.target!, /*oldProgram*/ undefined, useGetSourceFileByPath);
157149
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host);
158150
program.sourceTexts = texts;
159151
program.host = host;
160152
return program;
161153
}
162154

163-
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
155+
export function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: ReadonlyArray<string>, options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[], useGetSourceFileByPath?: boolean) {
164156
if (!newTexts) {
165157
newTexts = oldProgram.sourceTexts!.slice(0);
166158
}
167159
updater(newTexts);
168-
const host = createTestCompilerHost(newTexts, options.target!, oldProgram);
160+
const host = createTestCompilerHost(newTexts, options.target!, oldProgram, useGetSourceFileByPath);
169161
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host, oldProgram);
170162
program.sourceTexts = newTexts;
171163
program.host = host;
@@ -809,7 +801,7 @@ namespace ts {
809801
const root = "/a.ts";
810802
const compilerOptions = { target, moduleResolution: ModuleResolutionKind.NodeJs };
811803

812-
function createRedirectProgram(options?: { bText: string, bVersion: string }): ProgramWithSourceTexts {
804+
function createRedirectProgram(useGetSourceFileByPath: boolean, options?: { bText: string, bVersion: string }): ProgramWithSourceTexts {
813805
const files: NamedSourceText[] = [
814806
{
815807
name: "/node_modules/a/index.d.ts",
@@ -841,55 +833,64 @@ namespace ts {
841833
},
842834
];
843835

844-
return newProgram(files, [root], compilerOptions);
836+
return newProgram(files, [root], compilerOptions, useGetSourceFileByPath);
845837
}
846838

847-
function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void): ProgramWithSourceTexts {
848-
return updateProgram(program, [root], compilerOptions, updater);
839+
function updateRedirectProgram(program: ProgramWithSourceTexts, updater: (files: NamedSourceText[]) => void, useGetSourceFileByPath: boolean): ProgramWithSourceTexts {
840+
return updateProgram(program, [root], compilerOptions, updater, /*newTexts*/ undefined, useGetSourceFileByPath);
849841
}
850842

851-
it("No changes -> redirect not broken", () => {
852-
const program1 = createRedirectProgram();
843+
function verifyRedirects(useGetSourceFileByPath: boolean) {
844+
it("No changes -> redirect not broken", () => {
845+
const program1 = createRedirectProgram(useGetSourceFileByPath);
853846

854-
const program2 = updateRedirectProgram(program1, files => {
855-
updateProgramText(files, root, "const x = 1;");
847+
const program2 = updateRedirectProgram(program1, files => {
848+
updateProgramText(files, root, "const x = 1;");
849+
}, useGetSourceFileByPath);
850+
assert.equal(program1.structureIsReused, StructureIsReused.Completely);
851+
assert.lengthOf(program2.getSemanticDiagnostics(), 0);
856852
});
857-
assert.equal(program1.structureIsReused, StructureIsReused.Completely);
858-
assert.lengthOf(program2.getSemanticDiagnostics(), 0);
859-
});
860853

861-
it("Target changes -> redirect broken", () => {
862-
const program1 = createRedirectProgram();
863-
assert.lengthOf(program1.getSemanticDiagnostics(), 0);
854+
it("Target changes -> redirect broken", () => {
855+
const program1 = createRedirectProgram(useGetSourceFileByPath);
856+
assert.lengthOf(program1.getSemanticDiagnostics(), 0);
864857

865-
const program2 = updateRedirectProgram(program1, files => {
866-
updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }");
867-
updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }'));
858+
const program2 = updateRedirectProgram(program1, files => {
859+
updateProgramText(files, axIndex, "export default class X { private x: number; private y: number; }");
860+
updateProgramText(files, axPackage, JSON.stringify('{ name: "x", version: "1.2.4" }'));
861+
}, useGetSourceFileByPath);
862+
assert.equal(program1.structureIsReused, StructureIsReused.Not);
863+
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
868864
});
869-
assert.equal(program1.structureIsReused, StructureIsReused.Not);
870-
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
871-
});
872865

873-
it("Underlying changes -> redirect broken", () => {
874-
const program1 = createRedirectProgram();
866+
it("Underlying changes -> redirect broken", () => {
867+
const program1 = createRedirectProgram(useGetSourceFileByPath);
875868

876-
const program2 = updateRedirectProgram(program1, files => {
877-
updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }");
878-
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" }));
869+
const program2 = updateRedirectProgram(program1, files => {
870+
updateProgramText(files, bxIndex, "export default class X { private x: number; private y: number; }");
871+
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.4" }));
872+
}, useGetSourceFileByPath);
873+
assert.equal(program1.structureIsReused, StructureIsReused.Not);
874+
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
879875
});
880-
assert.equal(program1.structureIsReused, StructureIsReused.Not);
881-
assert.lengthOf(program2.getSemanticDiagnostics(), 1);
882-
});
883876

884-
it("Previously duplicate packages -> program structure not reused", () => {
885-
const program1 = createRedirectProgram({ bVersion: "1.2.4", bText: "export = class X { private x: number; }" });
877+
it("Previously duplicate packages -> program structure not reused", () => {
878+
const program1 = createRedirectProgram(useGetSourceFileByPath, { bVersion: "1.2.4", bText: "export = class X { private x: number; }" });
886879

887-
const program2 = updateRedirectProgram(program1, files => {
888-
updateProgramText(files, bxIndex, "export default class X { private x: number; }");
889-
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" }));
880+
const program2 = updateRedirectProgram(program1, files => {
881+
updateProgramText(files, bxIndex, "export default class X { private x: number; }");
882+
updateProgramText(files, bxPackage, JSON.stringify({ name: "x", version: "1.2.3" }));
883+
}, useGetSourceFileByPath);
884+
assert.equal(program1.structureIsReused, StructureIsReused.Not);
885+
assert.deepEqual(program2.getSemanticDiagnostics(), []);
890886
});
891-
assert.equal(program1.structureIsReused, StructureIsReused.Not);
892-
assert.deepEqual(program2.getSemanticDiagnostics(), []);
887+
}
888+
889+
describe("when host implements getSourceFile", () => {
890+
verifyRedirects(/*useGetSourceFileByPath*/ false);
891+
});
892+
describe("when host implements getSourceFileByPath", () => {
893+
verifyRedirects(/*useGetSourceFileByPath*/ true);
893894
});
894895
});
895896
});

0 commit comments

Comments
 (0)