@@ -14,7 +14,7 @@ import {IncrementalBuild} from '../../incremental/api';
14
14
import { ReflectionHost } from '../../reflection' ;
15
15
import { isShim } from '../../shims' ;
16
16
import { getSourceFileOrNull } from '../../util/src/typescript' ;
17
- import { ProgramTypeCheckAdapter , TemplateTypeChecker , TypeCheckingConfig , TypeCheckingProgramStrategy , UpdateMode } from '../api' ;
17
+ import { OptimizeFor , ProgramTypeCheckAdapter , TemplateTypeChecker , TypeCheckingConfig , TypeCheckingProgramStrategy , UpdateMode } from '../api' ;
18
18
19
19
import { InliningMode , ShimTypeCheckingData , TypeCheckContextImpl , TypeCheckingHost } from './context' ;
20
20
import { findTypeCheckBlock , shouldReportDiagnostic , TemplateSourceResolver , translateDiagnostic } from './diagnostics' ;
@@ -41,8 +41,15 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
41
41
* Retrieve type-checking diagnostics from the given `ts.SourceFile` using the most recent
42
42
* type-checking program.
43
43
*/
44
- getDiagnosticsForFile ( sf : ts . SourceFile ) : ts . Diagnostic [ ] {
45
- this . ensureAllShimsForAllFiles ( ) ;
44
+ getDiagnosticsForFile ( sf : ts . SourceFile , optimizeFor : OptimizeFor ) : ts . Diagnostic [ ] {
45
+ switch ( optimizeFor ) {
46
+ case OptimizeFor . WholeProgram :
47
+ this . ensureAllShimsForAllFiles ( ) ;
48
+ break ;
49
+ case OptimizeFor . SingleFile :
50
+ this . ensureAllShimsForOneFile ( sf ) ;
51
+ break ;
52
+ }
46
53
47
54
const sfPath = absoluteFromSourceFile ( sf ) ;
48
55
const fileRecord = this . state . get ( sfPath ) ! ;
@@ -68,7 +75,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
68
75
}
69
76
70
77
getTypeCheckBlock ( component : ts . ClassDeclaration ) : ts . Node | null {
71
- this . ensureAllShimsForAllFiles ( ) ;
78
+ this . ensureAllShimsForOneFile ( component . getSourceFile ( ) ) ;
72
79
73
80
const program = this . typeCheckingStrategy . getProgram ( ) ;
74
81
const filePath = absoluteFromSourceFile ( component . getSourceFile ( ) ) ;
@@ -81,7 +88,7 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
81
88
const id = fileRecord . sourceManager . getTemplateId ( component ) ;
82
89
83
90
const shimSf = getSourceFileOrNull ( program , shimPath ) ;
84
- if ( shimSf === null ) {
91
+ if ( shimSf === null || ! fileRecord . shimData . has ( shimPath ) ) {
85
92
throw new Error ( `Error: no shim file in program: ${ shimPath } ` ) ;
86
93
}
87
94
@@ -144,6 +151,27 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
144
151
this . isComplete = true ;
145
152
}
146
153
154
+ private ensureAllShimsForOneFile ( sf : ts . SourceFile ) : void {
155
+ this . maybeAdoptPriorResultsForFile ( sf ) ;
156
+
157
+ const sfPath = absoluteFromSourceFile ( sf ) ;
158
+
159
+ const fileData = this . getFileData ( sfPath ) ;
160
+ if ( fileData . isComplete ) {
161
+ // All data for this file is present and accounted for already.
162
+ return ;
163
+ }
164
+
165
+ const host = new SingleFileTypeCheckingHost ( sfPath , fileData , this . typeCheckingStrategy , this ) ;
166
+ const ctx = this . newContext ( host ) ;
167
+
168
+ this . typeCheckAdapter . typeCheck ( sf , ctx ) ;
169
+
170
+ fileData . isComplete = true ;
171
+
172
+ this . updateFromContext ( ctx ) ;
173
+ }
174
+
147
175
private newContext ( host : TypeCheckingHost ) : TypeCheckContextImpl {
148
176
const inlining = this . typeCheckingStrategy . supportsInlineOperations ? InliningMode . InlineOps :
149
177
InliningMode . Error ;
@@ -152,6 +180,30 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker {
152
180
host , inlining ) ;
153
181
}
154
182
183
+ /**
184
+ * Remove any shim data that depends on inline operations applied to the type-checking program.
185
+ *
186
+ * This can be useful if new inlines need to be applied, and it's not possible to guarantee that
187
+ * they won't overwrite or corrupt existing inlines that are used by such shims.
188
+ */
189
+ clearAllShimDataUsingInlines ( ) : void {
190
+ for ( const fileData of this . state . values ( ) ) {
191
+ if ( ! fileData . hasInlines ) {
192
+ continue ;
193
+ }
194
+
195
+ for ( const [ shimFile , shimData ] of fileData . shimData . entries ( ) ) {
196
+ if ( shimData . hasInlines ) {
197
+ fileData . shimData . delete ( shimFile ) ;
198
+ }
199
+ }
200
+
201
+ fileData . hasInlines = false ;
202
+ fileData . isComplete = false ;
203
+ this . isComplete = false ;
204
+ }
205
+ }
206
+
155
207
private updateFromContext ( ctx : TypeCheckContextImpl ) : void {
156
208
const updates = ctx . finalize ( ) ;
157
209
this . typeCheckingStrategy . updateFiles ( updates , UpdateMode . Incremental ) ;
@@ -240,3 +292,61 @@ class WholeProgramTypeCheckingHost implements TypeCheckingHost {
240
292
this . impl . getFileData ( sfPath ) . isComplete = true ;
241
293
}
242
294
}
295
+
296
+ /**
297
+ * Drives a `TypeCheckContext` to generate type-checking code efficiently for a single input file.
298
+ */
299
+ class SingleFileTypeCheckingHost implements TypeCheckingHost {
300
+ private seenInlines = false ;
301
+
302
+ constructor (
303
+ private sfPath : AbsoluteFsPath , private fileData : FileTypeCheckingData ,
304
+ private strategy : TypeCheckingProgramStrategy , private impl : TemplateTypeCheckerImpl ) { }
305
+
306
+ private assertPath ( sfPath : AbsoluteFsPath ) : void {
307
+ if ( this . sfPath !== sfPath ) {
308
+ throw new Error ( `AssertionError: querying TypeCheckingHost outside of assigned file` ) ;
309
+ }
310
+ }
311
+
312
+ getSourceManager ( sfPath : AbsoluteFsPath ) : TemplateSourceManager {
313
+ this . assertPath ( sfPath ) ;
314
+ return this . fileData . sourceManager ;
315
+ }
316
+
317
+ shouldCheckComponent ( node : ts . ClassDeclaration ) : boolean {
318
+ if ( this . sfPath !== absoluteFromSourceFile ( node . getSourceFile ( ) ) ) {
319
+ return false ;
320
+ }
321
+ const shimPath = this . strategy . shimPathForComponent ( node ) ;
322
+
323
+ // Only need to generate a TCB for the class if no shim exists for it currently.
324
+ return ! this . fileData . shimData . has ( shimPath ) ;
325
+ }
326
+
327
+ recordShimData ( sfPath : AbsoluteFsPath , data : ShimTypeCheckingData ) : void {
328
+ this . assertPath ( sfPath ) ;
329
+
330
+ // Previous type-checking state may have required the use of inlines (assuming they were
331
+ // supported). If the current operation also requires inlines, this presents a problem:
332
+ // generating new inlines may invalidate any old inlines that old state depends on.
333
+ //
334
+ // Rather than resolve this issue by tracking specific dependencies on inlines, if the new state
335
+ // relies on inlines, any old state that relied on them is simply cleared. This happens when the
336
+ // first new state that uses inlines is encountered.
337
+ if ( data . hasInlines && ! this . seenInlines ) {
338
+ this . impl . clearAllShimDataUsingInlines ( ) ;
339
+ this . seenInlines = true ;
340
+ }
341
+
342
+ this . fileData . shimData . set ( data . path , data ) ;
343
+ if ( data . hasInlines ) {
344
+ this . fileData . hasInlines = true ;
345
+ }
346
+ }
347
+
348
+ recordComplete ( sfPath : AbsoluteFsPath ) : void {
349
+ this . assertPath ( sfPath ) ;
350
+ this . fileData . isComplete = true ;
351
+ }
352
+ }
0 commit comments