@@ -329,6 +329,7 @@ function hasRegexConflict(rootNode: SgNode<TSX>): boolean {
329329
330330async function transform ( root : SgRoot < TSX > ) : Promise < string | null > {
331331 const rootNode = root . root ( ) ;
332+ const sourceText = rootNode . text ( ) ;
332333
333334 // Check if regex is already imported
334335 const importInfo = hasRegexImport ( rootNode ) ;
@@ -435,18 +436,49 @@ async function transform(root: SgRoot<TSX>): Promise<string | null> {
435436
436437 // Add TODO comment if either is dynamic
437438 if ( ! patternStatic || ! flagsStatic ) {
438- const containingStmt = expression . ancestors ( ) . find ( ( a : SgNode < TSX > ) =>
439- a . kind ( ) === "expression_statement" ||
440- a . kind ( ) === "variable_declaration" ||
441- a . kind ( ) === "return_statement"
442- ) ;
439+ // Find the start of the line where the expression is located
440+ const exprRange = expression . range ( ) ;
443441
444- if ( containingStmt && ! hasTodoComment ( containingStmt ) ) {
442+ // Find the start of the line (go backwards to find the previous newline)
443+ let lineStart = exprRange . start . index ;
444+ while ( lineStart > 0 && sourceText [ lineStart - 1 ] !== '\n' ) {
445+ lineStart -- ;
446+ }
447+
448+ // Check if there's already a TODO comment on this line or the line before
449+ // First, check the current line
450+ const lineEnd = sourceText . indexOf ( '\n' , lineStart ) ;
451+ const currentLineText = lineEnd >= 0
452+ ? sourceText . substring ( lineStart , lineEnd )
453+ : sourceText . substring ( lineStart ) ;
454+ const hasCommentOnCurrentLine = currentLineText . trim ( ) . startsWith ( "//" ) &&
455+ currentLineText . includes ( "TODO(arkregex)" ) ;
456+
457+ // Then check the previous line
458+ const lineBeforeStart = lineStart > 0 ? ( ( ) => {
459+ let pos = lineStart - 1 ;
460+ // Skip the newline itself
461+ if ( pos >= 0 && sourceText [ pos ] === '\n' ) pos -- ;
462+ // Go back to find the start of the previous line
463+ while ( pos > 0 && sourceText [ pos - 1 ] !== '\n' ) {
464+ pos -- ;
465+ }
466+ return pos ;
467+ } ) ( ) : - 1 ;
468+
469+ const hasCommentOnPreviousLine = lineBeforeStart >= 0 &&
470+ sourceText . substring ( lineBeforeStart , lineStart ) . includes ( "TODO(arkregex)" ) ;
471+
472+ const hasExistingComment = hasCommentOnCurrentLine || hasCommentOnPreviousLine ;
473+
474+ if ( ! hasExistingComment ) {
445475 const todoComment = "// TODO(arkregex): pattern/flags not statically known; typing may degrade. Consider regex.as<...>(...)\n" ;
446- const stmtRange = containingStmt . range ( ) ;
476+ // Place comment at the start of the current line (which will appear right above the expression)
477+ // If we're not at the start of the file, we're placing it on the line before
478+ const insertPos = lineStart > 0 ? lineStart : 0 ;
447479 edits . push ( {
448- startPos : stmtRange . start . index ,
449- endPos : stmtRange . start . index ,
480+ startPos : insertPos ,
481+ endPos : insertPos ,
450482 insertedText : todoComment ,
451483 } ) ;
452484 }
@@ -509,18 +541,49 @@ async function transform(root: SgRoot<TSX>): Promise<string | null> {
509541 }
510542
511543 if ( ! patternStatic || ! flagsStatic ) {
512- const containingStmt = call . ancestors ( ) . find ( ( a : SgNode < TSX > ) =>
513- a . kind ( ) === "expression_statement" ||
514- a . kind ( ) === "variable_declaration" ||
515- a . kind ( ) === "return_statement"
516- ) ;
544+ // Find the start of the line where the call is located
545+ const callRange = call . range ( ) ;
546+
547+ // Find the start of the line (go backwards to find the previous newline)
548+ let lineStart = callRange . start . index ;
549+ while ( lineStart > 0 && sourceText [ lineStart - 1 ] !== '\n' ) {
550+ lineStart -- ;
551+ }
552+
553+ // Check if there's already a TODO comment on this line or the line before
554+ // First, check the current line
555+ const lineEnd = sourceText . indexOf ( '\n' , lineStart ) ;
556+ const currentLineText = lineEnd >= 0
557+ ? sourceText . substring ( lineStart , lineEnd )
558+ : sourceText . substring ( lineStart ) ;
559+ const hasCommentOnCurrentLine = currentLineText . trim ( ) . startsWith ( "//" ) &&
560+ currentLineText . includes ( "TODO(arkregex)" ) ;
561+
562+ // Then check the previous line
563+ const lineBeforeStart = lineStart > 0 ? ( ( ) => {
564+ let pos = lineStart - 1 ;
565+ // Skip the newline itself
566+ if ( pos >= 0 && sourceText [ pos ] === '\n' ) pos -- ;
567+ // Go back to find the start of the previous line
568+ while ( pos > 0 && sourceText [ pos - 1 ] !== '\n' ) {
569+ pos -- ;
570+ }
571+ return pos ;
572+ } ) ( ) : - 1 ;
573+
574+ const hasCommentOnPreviousLine = lineBeforeStart >= 0 &&
575+ sourceText . substring ( lineBeforeStart , lineStart ) . includes ( "TODO(arkregex)" ) ;
576+
577+ const hasExistingComment = hasCommentOnCurrentLine || hasCommentOnPreviousLine ;
517578
518- if ( containingStmt && ! hasTodoComment ( containingStmt ) ) {
579+ if ( ! hasExistingComment ) {
519580 const todoComment = "// TODO(arkregex): pattern/flags not statically known; typing may degrade. Consider regex.as<...>(...)\n" ;
520- const stmtRange = containingStmt . range ( ) ;
581+ // Place comment at the start of the current line (which will appear right above the expression)
582+ // If we're not at the start of the file, we're placing it on the line before
583+ const insertPos = lineStart > 0 ? lineStart : 0 ;
521584 edits . push ( {
522- startPos : stmtRange . start . index ,
523- endPos : stmtRange . start . index ,
585+ startPos : insertPos ,
586+ endPos : insertPos ,
524587 insertedText : todoComment ,
525588 } ) ;
526589 }
0 commit comments