@@ -14,12 +14,12 @@ import ts from 'typescript';
1414//
1515// IMPORTANT: We intentionally avoid deep traversal to reduce chance of corrupting internal TS state.
1616// Only top-level/class-block statement arrays are rewritten.
17- export default function ( ctx : ts . TransformationContext ) {
18- const factory = ctx . factory ?? ts . factory ;
17+ export default function ( context : ts . TransformationContext , ... args ) {
18+ const factory = context . factory ?? ts . factory ;
1919 return ( sourceFile : ts . SourceFile ) => {
2020 if ( sourceFile . isDeclarationFile ) return sourceFile ;
2121 let mutated = false ;
22-
22+
2323 // Minimal mutable shape
2424 type MutableNode = ts . Node & {
2525 flags ?: ts . NodeFlags ;
@@ -78,7 +78,7 @@ export default function (ctx: ts.TransformationContext) {
7878 target : ts . ScriptTarget . ES5 ,
7979 noEmitHelpers : true ,
8080 experimentalDecorators : true ,
81- emitDecoratorMetadata : true ,
81+ emitDecoratorMetadata : false ,
8282 useDefineForClassFields : false ,
8383 } ,
8484 } )
@@ -182,8 +182,8 @@ export default function (ctx: ts.TransformationContext) {
182182 }
183183
184184 function visitNode ( node : ts . Node ) : ts . Node {
185- // Do not traverse synthesized helper trees; leave them intact
186- if ( ( ( node as MutableNode ) . flags ?? 0 ) & ts . NodeFlags . Synthesized ) {
185+ // Do not traverse synthesized helper trees; leave them intact
186+ if ( ( ( node as MutableNode ) . flags ?? 0 ) & ts . NodeFlags . Synthesized ) {
187187 return node ;
188188 }
189189 if ( ts . isSourceFile ( node ) ) {
@@ -210,71 +210,94 @@ export default function (ctx: ts.TransformationContext) {
210210 return changed ? factory . updateDefaultClause ( node , stmts ) : node ;
211211 }
212212 // No generic deep traversal; leave unrelated subtrees intact
213- return node ;
213+ return ts . visitEachChild ( node , visitNode , context ) ;
214214 }
215215
216216 function transformStatements (
217217 statements : ts . NodeArray < ts . Statement > ,
218218 isTopLevel : boolean ,
219219 ) : [ ts . NodeArray < ts . Statement > , boolean ] {
220220 let changed = false ;
221- const out : ts . Statement [ ] = [ ] ;
222- for ( const s of statements ) {
223- if ( ( ( s as MutableNode ) . flags ?? 0 ) & ts . NodeFlags . Synthesized ) {
224- out . push ( s ) ;
221+ if ( ! statements ) {
222+ return [ null , changed ] ;
223+ }
224+ const result : ts . Statement [ ] = [ ] ;
225+ for ( const statement of statements ) {
226+ if ( ( ( statement as MutableNode ) . flags ?? 0 ) & ts . NodeFlags . Synthesized ) {
227+ result . push ( statement ) ;
225228 continue ;
226229 }
227- if ( ts . isClassDeclaration ( s ) ) {
228- if ( hasNativeClassDecorator ( s ) ) {
230+ if ( ts . isClassDeclaration ( statement ) ) {
231+ if ( hasNativeClassDecorator ( statement ) ) {
229232 mutated = true ;
230233 changed = true ;
231- out . push ( ...emitDownleveledClass ( s ) ) ;
234+ result . push ( ...emitDownleveledClass ( statement ) ) ;
232235 continue ;
233236 } else {
234237 // As an extra fallback, raw text check in case decorator nodes failed and regex missed
235- const sf = s . getSourceFile ( ) ;
236- const start = ( s as any ) . getFullStart
237- ? ( s as any ) . getFullStart ( )
238- : ( s . pos ?? s . getStart ?.( sf ) ) ;
239- const raw = sf . text . slice ( start , s . end ) ;
238+ const sf = statement . getSourceFile ( ) ;
239+ const start = ( statement as any ) . getFullStart
240+ ? ( statement as any ) . getFullStart ( )
241+ : ( statement . pos ?? statement . getStart ?.( sf ) ) ;
242+ const raw = sf . text . slice ( start , statement . end ) ;
240243 if ( / ^ \s * @ N a t i v e C l a s s \b / m. test ( raw ) ) {
241244 mutated = true ;
242245 changed = true ;
243- out . push ( ...emitDownleveledClass ( s ) ) ;
246+ result . push ( ...emitDownleveledClass ( statement ) ) ;
244247 continue ;
245248 }
246249 }
247250 }
248- if ( ts . isExpressionStatement ( s ) ) {
249- const updated = removeNativeClassFromDecorate ( s ) ;
251+ if ( ts . isExpressionStatement ( statement ) ) {
252+ const updated = removeNativeClassFromDecorate ( statement ) ;
250253 if ( ! updated ) {
251254 mutated = true ;
252255 changed = true ;
253256 continue ;
254257 }
255- out . push ( updated ) ;
258+ const visited = ts . visitEachChild ( updated , visitNode , context ) ;
259+ if ( updated !== statement || visited !== statement ) {
260+ mutated = true ;
261+ changed = true ;
262+ }
263+ result . push ( visited ) ;
256264 continue ;
257265 }
258- if ( isTopLevel && ts . isImportDeclaration ( s ) ) {
259- const updated = removeNativeClassImport ( s ) ;
266+ if ( isTopLevel && ts . isImportDeclaration ( statement ) ) {
267+ const updated = removeNativeClassImport ( statement ) ;
260268 if ( ! updated ) {
261269 mutated = true ;
262270 changed = true ;
263271 continue ;
264272 }
265- if ( updated !== s ) {
273+ if ( updated !== statement ) {
266274 mutated = true ;
267275 changed = true ;
268276 }
269- out . push ( updated ) ;
277+ result . push ( updated ) ;
270278 continue ;
271279 }
272280 // No deep traversal for unrelated nodes
273- out . push ( s ) ;
281+ result . push ( statement ) ;
274282 }
275- return [ changed ? factory . createNodeArray ( out ) : statements , changed ] ;
283+ return [ changed ? factory . createNodeArray ( result ) : statements , changed ] ;
276284 }
277285
286+
287+ // we detect ts-patch
288+ if ( args . length ) {
289+ const statements = ts . visitNodes ( sourceFile . statements , visitNode ) as unknown as ts . Statement [ ] ;
290+ if ( ! mutated ) {
291+ return sourceFile ;
292+ }
293+ const updatedSource = factory . updateSourceFile ( sourceFile , statements as unknown as ts . Statement [ ] ) ;
294+ // Do NOT clear or rebind the entire SourceFile here. Doing so can break TS's
295+ // import usage analysis and lead to import elision. The factory/update API
296+ // preserves parents/bindings for original nodes (like imports). We only
297+ // synthesize/bind the newly inserted class replacement statements.
298+ return updatedSource ;
299+ }
300+
278301 const updated = ts . visitNode ( sourceFile , visitNode ) as ts . SourceFile ;
279302 if ( ! mutated ) return sourceFile ;
280303 return updated ;
@@ -316,4 +339,4 @@ function setSynthesizedRangeRecursive(node: ts.Node): void {
316339function setOriginalRecursive ( node : ts . Node , original : ts . Node ) : void {
317340 ts . setOriginalNode ( node , original ) ;
318341 ts . forEachChild ( node , ( child ) => setOriginalRecursive ( child , original ) ) ;
319- }
342+ }
0 commit comments