@@ -291,6 +291,329 @@ function sum(a, b) {
291291 } )
292292 } )
293293
294+ describe ( 'line-constrained search' , ( ) => {
295+ let strategy : SearchReplaceDiffStrategy
296+
297+ beforeEach ( ( ) => {
298+ strategy = new SearchReplaceDiffStrategy ( )
299+ } )
300+
301+ it ( 'should find and replace within specified line range' , ( ) => {
302+ const originalContent = `
303+ function one() {
304+ return 1;
305+ }
306+
307+ function two() {
308+ return 2;
309+ }
310+
311+ function three() {
312+ return 3;
313+ }
314+ ` . trim ( )
315+ const diffContent = `test.ts
316+ <<<<<<< SEARCH
317+ function two() {
318+ return 2;
319+ }
320+ =======
321+ function two() {
322+ return "two";
323+ }
324+ >>>>>>> REPLACE`
325+
326+ const result = strategy . applyDiff ( originalContent , diffContent , 5 , 7 )
327+ expect ( result ) . toBe ( `function one() {
328+ return 1;
329+ }
330+
331+ function two() {
332+ return "two";
333+ }
334+
335+ function three() {
336+ return 3;
337+ }` )
338+ } )
339+
340+ it ( 'should find and replace within buffer zone (5 lines before/after)' , ( ) => {
341+ const originalContent = `
342+ function one() {
343+ return 1;
344+ }
345+
346+ function two() {
347+ return 2;
348+ }
349+
350+ function three() {
351+ return 3;
352+ }
353+ ` . trim ( )
354+ const diffContent = `test.ts
355+ <<<<<<< SEARCH
356+ function three() {
357+ return 3;
358+ }
359+ =======
360+ function three() {
361+ return "three";
362+ }
363+ >>>>>>> REPLACE`
364+
365+ // Even though we specify lines 5-7, it should still find the match at lines 9-11
366+ // because it's within the 5-line buffer zone
367+ const result = strategy . applyDiff ( originalContent , diffContent , 5 , 7 )
368+ expect ( result ) . toBe ( `function one() {
369+ return 1;
370+ }
371+
372+ function two() {
373+ return 2;
374+ }
375+
376+ function three() {
377+ return "three";
378+ }` )
379+ } )
380+
381+ it ( 'should not find matches outside search range and buffer zone' , ( ) => {
382+ const originalContent = `
383+ function one() {
384+ return 1;
385+ }
386+
387+ function two() {
388+ return 2;
389+ }
390+
391+ function three() {
392+ return 3;
393+ }
394+
395+ function four() {
396+ return 4;
397+ }
398+
399+ function five() {
400+ return 5;
401+ }
402+ ` . trim ( )
403+ const diffContent = `test.ts
404+ <<<<<<< SEARCH
405+ function five() {
406+ return 5;
407+ }
408+ =======
409+ function five() {
410+ return "five";
411+ }
412+ >>>>>>> REPLACE`
413+
414+ // Searching around function two() (lines 5-7)
415+ // function five() is more than 5 lines away, so it shouldn't match
416+ const result = strategy . applyDiff ( originalContent , diffContent , 5 , 7 )
417+ expect ( result ) . toBe ( false )
418+ } )
419+
420+ it ( 'should handle search range at start of file' , ( ) => {
421+ const originalContent = `
422+ function one() {
423+ return 1;
424+ }
425+
426+ function two() {
427+ return 2;
428+ }
429+ ` . trim ( )
430+ const diffContent = `test.ts
431+ <<<<<<< SEARCH
432+ function one() {
433+ return 1;
434+ }
435+ =======
436+ function one() {
437+ return "one";
438+ }
439+ >>>>>>> REPLACE`
440+
441+ const result = strategy . applyDiff ( originalContent , diffContent , 1 , 3 )
442+ expect ( result ) . toBe ( `function one() {
443+ return "one";
444+ }
445+
446+ function two() {
447+ return 2;
448+ }` )
449+ } )
450+
451+ it ( 'should handle search range at end of file' , ( ) => {
452+ const originalContent = `
453+ function one() {
454+ return 1;
455+ }
456+
457+ function two() {
458+ return 2;
459+ }
460+ ` . trim ( )
461+ const diffContent = `test.ts
462+ <<<<<<< SEARCH
463+ function two() {
464+ return 2;
465+ }
466+ =======
467+ function two() {
468+ return "two";
469+ }
470+ >>>>>>> REPLACE`
471+
472+ const result = strategy . applyDiff ( originalContent , diffContent , 5 , 7 )
473+ expect ( result ) . toBe ( `function one() {
474+ return 1;
475+ }
476+
477+ function two() {
478+ return "two";
479+ }` )
480+ } )
481+
482+ it ( 'should match specific instance of duplicate code using line numbers' , ( ) => {
483+ const originalContent = `
484+ function processData(data) {
485+ return data.map(x => x * 2);
486+ }
487+
488+ function unrelatedStuff() {
489+ console.log("hello");
490+ }
491+
492+ // Another data processor
493+ function processData(data) {
494+ return data.map(x => x * 2);
495+ }
496+
497+ function moreStuff() {
498+ console.log("world");
499+ }
500+ ` . trim ( )
501+ const diffContent = `test.ts
502+ <<<<<<< SEARCH
503+ function processData(data) {
504+ return data.map(x => x * 2);
505+ }
506+ =======
507+ function processData(data) {
508+ // Add logging
509+ console.log("Processing data...");
510+ return data.map(x => x * 2);
511+ }
512+ >>>>>>> REPLACE`
513+
514+ // Target the second instance of processData
515+ const result = strategy . applyDiff ( originalContent , diffContent , 10 , 12 )
516+ expect ( result ) . toBe ( `function processData(data) {
517+ return data.map(x => x * 2);
518+ }
519+
520+ function unrelatedStuff() {
521+ console.log("hello");
522+ }
523+
524+ // Another data processor
525+ function processData(data) {
526+ // Add logging
527+ console.log("Processing data...");
528+ return data.map(x => x * 2);
529+ }
530+
531+ function moreStuff() {
532+ console.log("world");
533+ }` )
534+ } )
535+
536+ it ( 'should search from start line to end of file when only start_line is provided' , ( ) => {
537+ const originalContent = `
538+ function one() {
539+ return 1;
540+ }
541+
542+ function two() {
543+ return 2;
544+ }
545+
546+ function three() {
547+ return 3;
548+ }
549+ ` . trim ( )
550+ const diffContent = `test.ts
551+ <<<<<<< SEARCH
552+ function three() {
553+ return 3;
554+ }
555+ =======
556+ function three() {
557+ return "three";
558+ }
559+ >>>>>>> REPLACE`
560+
561+ // Only provide start_line, should search from there to end of file
562+ const result = strategy . applyDiff ( originalContent , diffContent , 8 )
563+ expect ( result ) . toBe ( `function one() {
564+ return 1;
565+ }
566+
567+ function two() {
568+ return 2;
569+ }
570+
571+ function three() {
572+ return "three";
573+ }` )
574+ } )
575+
576+ it ( 'should search from start of file to end line when only end_line is provided' , ( ) => {
577+ const originalContent = `
578+ function one() {
579+ return 1;
580+ }
581+
582+ function two() {
583+ return 2;
584+ }
585+
586+ function three() {
587+ return 3;
588+ }
589+ ` . trim ( )
590+ const diffContent = `test.ts
591+ <<<<<<< SEARCH
592+ function one() {
593+ return 1;
594+ }
595+ =======
596+ function one() {
597+ return "one";
598+ }
599+ >>>>>>> REPLACE`
600+
601+ // Only provide end_line, should search from start of file to there
602+ const result = strategy . applyDiff ( originalContent , diffContent , undefined , 4 )
603+ expect ( result ) . toBe ( `function one() {
604+ return "one";
605+ }
606+
607+ function two() {
608+ return 2;
609+ }
610+
611+ function three() {
612+ return 3;
613+ }` )
614+ } )
615+ } )
616+
294617 describe ( 'getToolDescription' , ( ) => {
295618 let strategy : SearchReplaceDiffStrategy
296619
@@ -312,5 +635,11 @@ function sum(a, b) {
312635 expect ( description ) . toContain ( '<apply_diff>' )
313636 expect ( description ) . toContain ( '</apply_diff>' )
314637 } )
638+
639+ it ( 'should document start_line and end_line parameters' , ( ) => {
640+ const description = strategy . getToolDescription ( '/test' )
641+ expect ( description ) . toContain ( 'start_line: (required) The line number where the search block starts.' )
642+ expect ( description ) . toContain ( 'end_line: (required) The line number where the search block ends.' )
643+ } )
315644 } )
316645} )
0 commit comments