Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ae0ec73

Browse filesBrowse files
authored
fix(compiler-cli): re-tag SourceFiles after TsCreateProgramDriver.updateFiles()
TypeScript reuses SourceFile objects between old and new programs, so untagging the old program also untags shared files in the new program. Re-apply shim tags on the new program to prevent getSemanticDiagnostics() crashes with TS 5.5+.
1 parent ca37790 commit ae0ec73
Copy full SHA for ae0ec73

2 files changed

+161-1Lines changed: 161 additions & 1 deletion

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts‎

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/program_driver/src/ts_create_program_driver.ts
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,5 +272,12 @@ export class TsCreateProgramDriver implements ProgramDriver {
272272
// TS 5.5 not having the files tagged while producing diagnostics can lead to errors. See:
273273
// https://github.com/microsoft/TypeScript/pull/58398
274274
untagAllTsFiles(oldProgram);
275+
276+
// Re-tag the new program's files. Since TS reuses SourceFile objects between old and new
277+
// programs, untagging the old program also untags shared files in the new program.
278+
// Without re-tagging, getSemanticDiagnostics() crashes with "Cannot destructure property
279+
// 'pos' of 'file.referencedFiles[index]'" because fileProcessingDiagnostics recorded
280+
// indices into the tagged (longer) referencedFiles array during program creation.
281+
retagAllTsFiles(this.program);
275282
}
276283
}
Collapse file

‎packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts‎

Copy file name to clipboardExpand all lines: packages/compiler-cli/src/ngtsc/typecheck/test/program_spec.ts
+154-1Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ts from 'typescript';
1111
import {absoluteFrom, AbsoluteFsPath, getSourceFileOrError} from '../../file_system';
1212
import {runInEachFileSystem} from '../../file_system/testing';
1313
import {FileUpdate, TsCreateProgramDriver, UpdateMode} from '../../program_driver';
14-
import {sfExtensionData, ShimReferenceTagger} from '../../shims';
14+
import {sfExtensionData, ShimReferenceTagger, untagAllTsFiles} from '../../shims';
1515
import {expectCompleteReuse, makeProgram} from '../../testing';
1616
import {OptimizeFor} from '../api';
1717

@@ -64,9 +64,89 @@ runInEachFileSystem(() => {
6464

6565
expectCompleteReuse(programStrategy.getProgram());
6666
});
67+
68+
it('should retain shim tags on shared SourceFiles after updateFiles', () => {
69+
assertShimTagsRetainedAfterUpdate(UpdateMode.Complete);
70+
});
71+
72+
it('should retain shim tags on shared SourceFiles after incremental updateFiles', () => {
73+
const {program, host, options, typecheckPath, mainPath} =
74+
makeProgramWhereShimIsIncludedViaReference();
75+
76+
const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']);
77+
78+
programStrategy.updateFiles(
79+
new Map([[typecheckPath, createUpdate('export const VERSION = 2;')]]),
80+
UpdateMode.Complete,
81+
);
82+
programStrategy.updateFiles(
83+
new Map([[typecheckPath, createUpdate('export const VERSION = 3;')]]),
84+
UpdateMode.Incremental,
85+
);
86+
87+
assertSharedSourceFileShimTags(programStrategy.getProgram(), mainPath);
88+
});
89+
90+
it('should allow untagging the input program for emit after updateFiles', () => {
91+
const {program, host, options, typecheckPath, mainPath} =
92+
makeProgramWhereShimIsIncludedViaReference();
93+
94+
const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']);
95+
96+
// Update only the shim file so /main.ts remains a shared SourceFile between programs.
97+
programStrategy.updateFiles(
98+
new Map([[typecheckPath, createUpdate('export const VERSION = 2;')]]),
99+
UpdateMode.Complete,
100+
);
101+
102+
const mainSf = getSourceFileOrError(program, mainPath);
103+
const ext = sfExtensionData(mainSf);
104+
105+
expect(ext.taggedReferenceFiles).not.toBeNull();
106+
expect(mainSf.referencedFiles.length).toEqual(ext.taggedReferenceFiles!.length);
107+
108+
// prepareEmit() untags the input program before emit (#56945).
109+
untagAllTsFiles(program);
110+
111+
expect(ext.originalReferencedFiles).not.toBeNull();
112+
expect(mainSf.referencedFiles).toEqual(ext.originalReferencedFiles!);
113+
for (const ref of mainSf.referencedFiles) {
114+
expect(ref.fileName).not.toContain('ngtypecheck');
115+
}
116+
});
67117
});
68118
});
69119

120+
function assertShimTagsRetainedAfterUpdate(updateMode: UpdateMode): void {
121+
const {program, host, options, typecheckPath, mainPath} =
122+
makeProgramWhereShimIsIncludedViaReference();
123+
124+
const programStrategy = new TsCreateProgramDriver(program, host, options, ['ngtypecheck']);
125+
126+
// Update only the shim file so /main.ts remains a shared SourceFile between programs.
127+
programStrategy.updateFiles(
128+
new Map([[typecheckPath, createUpdate('export const VERSION = 2;')]]),
129+
updateMode,
130+
);
131+
132+
assertSharedSourceFileShimTags(programStrategy.getProgram(), mainPath);
133+
}
134+
135+
function assertSharedSourceFileShimTags(program: ts.Program, mainPath: AbsoluteFsPath): void {
136+
const mainSf = getSourceFileOrError(program, mainPath);
137+
const ext = sfExtensionData(mainSf);
138+
139+
// Without the fix, untagging the old program also untags shared SourceFiles in the new
140+
// program, leaving referencedFiles shorter than taggedReferenceFiles.
141+
expect(ext.taggedReferenceFiles).not.toBeNull();
142+
expect(mainSf.referencedFiles.length).toEqual(ext.taggedReferenceFiles!.length);
143+
144+
// Replicates the TS 5.5+ crash from #68164: with composite, a shim included only via
145+
// referencedFiles gets a lazy "not listed in project" diagnostic. Expanding that diagnostic
146+
// walks the ReferenceFile include reason and reads main.referencedFiles[index].
147+
expect(() => program.getSemanticDiagnostics(mainSf)).not.toThrow();
148+
}
149+
70150
function createUpdate(text: string): FileUpdate {
71151
return {
72152
newText: text,
@@ -116,3 +196,76 @@ function makeSingleFileProgramWithTypecheckShim(): {
116196

117197
return {program, host, options, mainPath, typecheckPath};
118198
}
199+
200+
/**
201+
* Build a program where the ngtypecheck shim is pulled in only through a tagged
202+
* `referencedFiles` entry (not as a root). Combined with `composite`, TypeScript records a lazy
203+
* project-file-list diagnostic whose expansion exercises `referencedFiles[index]` — the path that
204+
* crashes when shared SourceFiles are untagged after `updateFiles()`.
205+
*/
206+
function makeProgramWhereShimIsIncludedViaReference(): {
207+
program: ts.Program;
208+
host: ts.CompilerHost;
209+
options: ts.CompilerOptions;
210+
mainPath: AbsoluteFsPath;
211+
typecheckPath: AbsoluteFsPath;
212+
} {
213+
const mainPath = absoluteFrom('/main.ts');
214+
const typecheckPath = absoluteFrom('/main.ngtypecheck.ts');
215+
const {host, options} = makeProgram(
216+
[
217+
{
218+
name: mainPath,
219+
contents: 'export const NOT_A_COMPONENT = true;',
220+
},
221+
{
222+
name: typecheckPath,
223+
contents: 'export const VERSION = 1;',
224+
isRoot: false,
225+
},
226+
],
227+
{
228+
composite: true,
229+
declaration: true,
230+
},
231+
/* host */ undefined,
232+
/* checkForErrors */ false,
233+
);
234+
235+
const shimTagger = new ShimReferenceTagger(['ngtypecheck']);
236+
// Inherit prototype methods from the real host; object spread would drop them.
237+
const taggingHost = Object.create(host) as ts.CompilerHost;
238+
taggingHost.getSourceFile = (
239+
fileName: string,
240+
languageVersionOrOptions: ts.ScriptTarget | ts.CreateSourceFileOptions,
241+
onError?: (message: string) => void,
242+
shouldCreateNewSourceFile?: boolean,
243+
): ts.SourceFile | undefined => {
244+
const sf = host.getSourceFile(
245+
fileName,
246+
languageVersionOrOptions,
247+
onError,
248+
shouldCreateNewSourceFile,
249+
);
250+
if (sf !== undefined) {
251+
shimTagger.tag(sf);
252+
}
253+
return sf;
254+
};
255+
256+
const program = ts.createProgram({
257+
rootNames: [host.getCanonicalFileName(mainPath)],
258+
options,
259+
host: taggingHost,
260+
});
261+
shimTagger.finalize();
262+
263+
const typecheckSf = getSourceFileOrError(program, typecheckPath);
264+
sfExtensionData(typecheckSf).fileShim = {
265+
extension: 'ngtypecheck',
266+
generatedFrom: mainPath,
267+
};
268+
269+
// Return the unwrapped host; TsCreateProgramDriver installs its own tagging host on updates.
270+
return {program, host, options, mainPath, typecheckPath};
271+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.