@@ -117,6 +117,8 @@ public static function transformSource(string $source, string $filePath = ''): s
117117 return $ source ;
118118 }
119119
120+ $ originalLineCount = substr_count ($ source , "\n" );
121+
120122 $ parser = (new ParserFactory ())->createForNewestSupportedVersion ();
121123
122124 try {
@@ -150,9 +152,26 @@ public static function transformSource(string $source, string $filePath = ''): s
150152 $ printer = new TypePHPPrinter ();
151153 $ transformed = $ printer ->printFormatPreserving ($ newStmts , $ oldStmts , $ oldTokens );
152154
153- // Critical: Remove the newline and indentation preceding any injected statement.
154- $ transformed = preg_replace ('/[ \t]*\r?\n[ \t]*\/\*__TYPEPHP_INJECTED__\*\// ' , ' /*__TYPEPHP_INJECTED__*/ ' , $ transformed ) ?? $ transformed ;
155- $ transformed = str_replace ('/*__TYPEPHP_INJECTED__*/ ' , '' , $ transformed );
155+ $ transformed = preg_replace ('/[ \t]*\r?\n[ \t]*\/\*__TYPEPHP_INJECTED_START__\*\// ' , ' /*__TYPEPHP_INJECTED_START__*/ ' , $ transformed ) ?? $ transformed ;
156+
157+ $ transformedLineCount = substr_count ($ transformed , "\n" );
158+ $ drift = $ transformedLineCount - $ originalLineCount ;
159+
160+ if ($ drift > 0 ) {
161+ $ transformed = preg_replace ('/[ \t]*\r?\n[ \t]*\{[ \t]*\/\*__TYPEPHP_INJECTED_START__\*\// ' , ' { /*__TYPEPHP_INJECTED_START__*/ ' , $ transformed , $ drift , $ count1 ) ?? $ transformed ;
162+ $ drift -= $ count1 ;
163+ }
164+
165+ if ($ drift > 0 ) {
166+ $ transformed = preg_replace ('/\/\*__TYPEPHP_INJECTED_END__\*\/[ \t]*\r?\n[ \t]*\}/ ' , '/*__TYPEPHP_INJECTED_END__*/ } ' , $ transformed , $ drift , $ count2 ) ?? $ transformed ;
167+ $ drift -= $ count2 ;
168+ }
169+
170+ if ($ drift > 0 ) {
171+ $ transformed = preg_replace ('/\/\*__TYPEPHP_INJECTED_END__\*\/[ \t]*\r?\n[ \t]*/ ' , '/*__TYPEPHP_INJECTED_END__*/ ' , $ transformed , $ drift ) ?? $ transformed ;
172+ }
173+
174+ $ transformed = str_replace (['/*__TYPEPHP_INJECTED_START__*/ ' , '/*__TYPEPHP_INJECTED_END__*/ ' ], '' , $ transformed );
156175
157176 return $ transformed ;
158177 }
@@ -485,7 +504,7 @@ private static function isReadOnlyCall(): bool
485504 return \in_array ($ callerFunc , ['file_get_contents ' , 'file ' , 'readfile ' , 'highlight_file ' , 'show_source ' , 'token_get_all ' ], true );
486505 }
487506
488- /**
507+ /**
489508 * Determines whether a target PHP file path should be intercepted using Pattern Specificity.
490509 */
491510 private static function isApplicationFile (string $ path , string |false $ resolvedPath ): bool
@@ -500,13 +519,20 @@ private static function isApplicationFile(string $path, string|false $resolvedPa
500519
501520 $ normalizedPath = str_replace ('\\' , '/ ' , $ resolvedPath );
502521
522+ // Prevent parsing TypePHP's own source code
503523 $ parentDir = realpath (__DIR__ . '/.. ' );
504524 $ libSrcDir = $ parentDir !== false ? str_replace ('\\' , '/ ' , $ parentDir ) : '' ;
505525
506526 if ($ libSrcDir !== '' && str_starts_with ($ normalizedPath , $ libSrcDir )) {
507527 return false ;
508528 }
509529
530+ // Unconditionally prevent double-parsing cached files!
531+ $ normalizedCacheDir = rtrim (str_replace ('\\' , '/ ' , self ::$ cacheDir ), '/ ' ) . '/ ' ;
532+ if (str_starts_with ($ normalizedPath , $ normalizedCacheDir )) {
533+ return false ;
534+ }
535+
510536 $ longestIncludeMatch = 0 ;
511537 foreach (self ::$ includeRawPatterns as $ pattern => $ regex ) {
512538 if (preg_match ($ regex , $ normalizedPath ) === 1 ) {
0 commit comments