@@ -30,7 +30,7 @@ import {ErrorCode, makeDiagnostic, ngErrorCode} from '../../../../src/ngtsc/diag
3030import { absoluteFromSourceFile , AbsoluteFsPath } from '../../file_system' ;
3131import { Reference , ReferenceEmitter } from '../../imports' ;
3232import { PerfEvent , PerfRecorder } from '../../perf' ;
33- import { FileUpdate } from '../../program_driver' ;
33+ import { FileUpdate , InliningMode } from '../../program_driver' ;
3434import { ClassDeclaration , ReflectionHost } from '../../reflection' ;
3535import { ImportManager } from '../../translator' ;
3636import {
@@ -125,6 +125,11 @@ export interface PendingFileTypeCheckingData {
125125 * Map of in-progress shim data for shims generated from this input file.
126126 */
127127 shimData : Map < AbsoluteFsPath , PendingShimData > ;
128+
129+ /**
130+ * The original source file.
131+ */
132+ sourceFile ?: ts . SourceFile ;
128133}
129134
130135export interface PendingShimData {
@@ -188,21 +193,6 @@ export interface TypeCheckingHost {
188193 recordComplete ( sfPath : AbsoluteFsPath ) : void ;
189194}
190195
191- /**
192- * How a type-checking context should handle operations which would require inlining.
193- */
194- export enum InliningMode {
195- /**
196- * Use inlining operations when required.
197- */
198- InlineOps ,
199-
200- /**
201- * Produce diagnostics if an operation would require inlining.
202- */
203- Error ,
204- }
205-
206196/**
207197 * A template type checking context for a program.
208198 *
@@ -391,11 +381,16 @@ export class TypeCheckContextImpl implements TypeCheckContext {
391381 this . perf . eventCount ( PerfEvent . GenerateTcb ) ;
392382 if (
393383 inliningRequirement !== TcbInliningRequirement . None &&
394- this . inlining === InliningMode . InlineOps
384+ ( this . inlining === InliningMode . InlineOps || this . inlining === InliningMode . CopySourceToTcb )
395385 ) {
396- // This class didn't meet the requirements for external type checking, so generate an inline
397- // TCB for the class .
386+ // Queue operations for both inline and copy strategy!
387+ // The decision on where to apply them will be made in finalize() .
398388 this . addInlineTypeCheckBlock ( fileData , shimData , ref , meta ) ;
389+
390+ if ( this . inlining === InliningMode . CopySourceToTcb ) {
391+ // Still set the original file path to force local references for symbols from this file.
392+ shimData . file . copiedSourceOriginPath = absoluteFromSourceFile ( sourceFile ) ;
393+ }
399394 } else if (
400395 inliningRequirement === TcbInliningRequirement . ShouldInlineForGenericBounds &&
401396 this . inlining === InliningMode . Error
@@ -451,18 +446,9 @@ export class TypeCheckContextImpl implements TypeCheckContext {
451446 }
452447
453448 /**
454- * Transform a `ts.SourceFile` into a version that includes type checking code.
455- *
456- * If this particular `ts.SourceFile` requires changes, the text representing its new contents
457- * will be returned. Otherwise, a `null` return indicates no changes were necessary.
449+ * Applies operations to a file.
458450 */
459- transform ( sf : ts . SourceFile ) : string | null {
460- // If there are no operations pending for this particular file, return `null` to indicate no
461- // changes.
462- if ( ! this . opMap . has ( sf ) ) {
463- return null ;
464- }
465-
451+ private executeOperations ( targetSf : ts . SourceFile , opsSourceSf : ts . SourceFile ) : string {
466452 // Use a `ts.Printer` to generate source code.
467453 const printer = ts . createPrinter ( { omitTrailingSemicolon : true } ) ;
468454
@@ -479,39 +465,43 @@ export class TypeCheckContextImpl implements TypeCheckContext {
479465 // Execute ops.
480466 // Each Op has a splitPoint index into the text where it needs to be inserted.
481467 const updates : { pos : number ; deletePos ?: number ; text : string } [ ] = this . opMap
482- . get ( sf ) !
468+ . get ( opsSourceSf ) !
483469 . map ( ( op ) => {
484470 return {
485471 pos : op . splitPoint ,
486- text : op . execute ( importManager , sf , this . refEmitter ) ,
472+ text : op . execute ( importManager , targetSf , this . refEmitter ) ,
487473 } ;
488474 } ) ;
489475
490476 const { newImports, updatedImports} = importManager . finalize ( ) ;
491477
492478 // Capture new imports
493- if ( newImports . has ( sf . fileName ) ) {
494- newImports . get ( sf . fileName ) ! . forEach ( ( newImport ) => {
479+ if ( newImports . has ( targetSf . fileName ) ) {
480+ newImports . get ( targetSf . fileName ) ! . forEach ( ( newImport ) => {
495481 updates . push ( {
496482 pos : 0 ,
497- text : printer . printNode ( ts . EmitHint . Unspecified , newImport , sf ) ,
483+ text : printer . printNode ( ts . EmitHint . Unspecified , newImport , targetSf ) ,
498484 } ) ;
499485 } ) ;
500486 }
501487
502488 // Capture updated imports
503489 for ( const [ oldBindings , newBindings ] of updatedImports . entries ( ) ) {
504- if ( oldBindings . getSourceFile ( ) !== sf ) {
490+ if ( oldBindings . getSourceFile ( ) !== targetSf ) {
505491 throw new Error ( 'Unexpected updates to unrelated source files.' ) ;
506492 }
507493 updates . push ( {
508494 pos : oldBindings . getStart ( ) ,
509495 deletePos : oldBindings . getEnd ( ) ,
510- text : printer . printNode ( ts . EmitHint . Unspecified , newBindings , sf ) ,
496+ text : printer . printNode ( ts . EmitHint . Unspecified , newBindings , targetSf ) ,
511497 } ) ;
512498 }
513499
514- const result = new MagicString ( sf . text , { filename : sf . fileName } ) ;
500+ // TODO: Consider generating a sourcemap here via `result.generateMap()`.
501+ // This could be used in `CopySourceToTcb` mode to map positions in the shim file
502+ // back to the original source file, helping with language features like "Go to Definition"
503+ // and diagnostic translation.
504+ const result = new MagicString ( targetSf . text , { filename : targetSf . fileName } ) ;
515505 for ( const update of updates ) {
516506 if ( update . deletePos !== undefined ) {
517507 result . remove ( update . pos , update . deletePos ) ;
@@ -521,11 +511,35 @@ export class TypeCheckContextImpl implements TypeCheckContext {
521511 return result . toString ( ) ;
522512 }
523513
514+ /**
515+ * Generates the transformed text for an original source file.
516+ */
517+ private generateTransformedOriginalFile ( sf : ts . SourceFile ) : string | null {
518+ if ( this . inlining !== InliningMode . InlineOps || ! this . opMap . has ( sf ) ) {
519+ return null ;
520+ }
521+ return this . executeOperations ( sf , sf ) ;
522+ }
523+
524+ /**
525+ * Generates the content for a shim file that copies the source of the original file.
526+ */
527+ private generateCopiedShimContent (
528+ originalSf : ts . SourceFile ,
529+ shimFileName : string ,
530+ ) : string | null {
531+ if ( this . inlining !== InliningMode . CopySourceToTcb || ! this . opMap . has ( originalSf ) ) {
532+ return null ;
533+ }
534+ const fakeSf = ts . createSourceFile ( shimFileName , originalSf . text , ts . ScriptTarget . Latest , true ) ;
535+ return this . executeOperations ( fakeSf , originalSf ) ;
536+ }
537+
524538 finalize ( ) : Map < AbsoluteFsPath , FileUpdate > {
525539 // First, build the map of updates to source files.
526540 const updates = new Map < AbsoluteFsPath , FileUpdate > ( ) ;
527541 for ( const originalSf of this . opMap . keys ( ) ) {
528- const newText = this . transform ( originalSf ) ;
542+ const newText = this . generateTransformedOriginalFile ( originalSf ) ;
529543 if ( newText !== null ) {
530544 updates . set ( absoluteFromSourceFile ( originalSf ) , {
531545 newText,
@@ -553,6 +567,19 @@ export class TypeCheckContextImpl implements TypeCheckContext {
553567 path : pendingShimData . file . fileName ,
554568 data : pendingShimData . data ,
555569 } ) ;
570+
571+ // Set the source content on the shim file before rendering!
572+ const originalSf = pendingFileData . sourceFile ;
573+ if ( originalSf !== undefined ) {
574+ const transformedText = this . generateCopiedShimContent (
575+ originalSf ,
576+ pendingShimData . file . fileName ,
577+ ) ;
578+ if ( transformedText !== null ) {
579+ pendingShimData . file . setSourceContent ( transformedText ) ;
580+ }
581+ }
582+
556583 const sfText = pendingShimData . file . render ( ) ;
557584 updates . set ( pendingShimData . file . fileName , {
558585 newText : sfText ,
@@ -587,6 +614,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
587614 shimData . oobRecorder ,
588615 ) ,
589616 ) ;
617+
590618 fileData . hasInlines = true ;
591619 }
592620
@@ -615,6 +643,7 @@ export class TypeCheckContextImpl implements TypeCheckContext {
615643 hasInlines : false ,
616644 sourceManager : this . host . getSourceManager ( sfPath ) ,
617645 shimData : new Map ( ) ,
646+ sourceFile : sf ,
618647 } ;
619648 this . fileMap . set ( sfPath , data ) ;
620649 }
@@ -690,8 +719,12 @@ class InlineTcbOp implements Op {
690719 return this . ref . node . end + 1 ;
691720 }
692721
693- execute ( im : ImportManager , sf : ts . SourceFile , refEmitter : ReferenceEmitter ) : string {
694- const env = new Environment ( this . config , im , refEmitter , sf ) ;
722+ execute ( im : ImportManager , tcbSf : ts . SourceFile , refEmitter : ReferenceEmitter ) : string {
723+ const env = new Environment ( this . config , im , refEmitter , tcbSf ) ;
724+ const originalSf = this . ref . node . getSourceFile ( ) ;
725+ if ( tcbSf !== originalSf ) {
726+ env . copiedSourceOriginPath = absoluteFromSourceFile ( originalSf ) ;
727+ }
695728 const fnName = `_tcb_${ this . ref . node . pos } ` ;
696729
697730 const { tcbMeta, component} = adaptTypeCheckBlockMetadata (
0 commit comments