@@ -387,6 +387,63 @@ impl Span {
387387 Self :: new ( self . start , end)
388388 }
389389
390+ /// Create a [`Span`] that has its start and end position moved to the left by
391+ /// `offset` bytes.
392+ ///
393+ /// # Example
394+ ///
395+ /// ```
396+ /// use oxc_span::Span;
397+ ///
398+ /// let a = Span::new(5, 10);
399+ /// let moved = a.move_left(5);
400+ /// assert_eq!(moved, Span::new(0, 5));
401+ ///
402+ /// // Moving the start over 0 is logical error that will panic in debug builds.
403+ /// std::panic::catch_unwind(|| {
404+ /// moved.move_left(5);
405+ /// });
406+ /// ```
407+ #[ must_use]
408+ pub const fn move_left ( self , offset : u32 ) -> Self {
409+ let start = self . start . saturating_sub ( offset) ;
410+ #[ cfg( debug_assertions) ]
411+ if start == 0 {
412+ debug_assert ! ( self . start == offset, "Cannot move span past zero length" ) ;
413+ }
414+ Self :: new ( start, self . end . saturating_sub ( offset) )
415+ }
416+
417+ /// Create a [`Span`] that has its start and end position moved to the right by
418+ /// `offset` bytes.
419+ ///
420+ /// # Example
421+ ///
422+ /// ```
423+ /// use oxc_span::Span;
424+ ///
425+ /// let a = Span::new(5, 10);
426+ /// let moved = a.move_right(5);
427+ /// assert_eq!(moved, Span::new(10, 15));
428+ ///
429+ /// // Moving the end over `u32::MAX` is logical error that will panic in debug builds.
430+ /// std::panic::catch_unwind(|| {
431+ /// moved.move_right(u32::MAX);
432+ /// });
433+ /// ```
434+ #[ must_use]
435+ pub const fn move_right ( self , offset : u32 ) -> Self {
436+ let end = self . end . saturating_add ( offset) ;
437+ #[ cfg( debug_assertions) ]
438+ if end == u32:: MAX {
439+ debug_assert ! (
440+ u32 :: MAX . saturating_sub( offset) == self . end,
441+ "Cannot move span past `u32::MAX` length"
442+ ) ;
443+ }
444+ Self :: new ( self . start . saturating_add ( offset) , end)
445+ }
446+
390447 /// Get a snippet of text from a source string that the [`Span`] covers.
391448 ///
392449 /// # Example
@@ -715,6 +772,39 @@ mod test {
715772 let span = Span :: new ( 5 , 10 ) ;
716773 let _ = span. shrink ( 5 ) ;
717774 }
775+
776+ #[ test]
777+ fn test_move_left ( ) {
778+ let span = Span :: new ( 5 , 10 ) ;
779+ assert_eq ! ( span. move_left( 1 ) , Span :: new( 4 , 9 ) ) ;
780+ assert_eq ! ( span. move_left( 2 ) , Span :: new( 3 , 8 ) ) ;
781+ assert_eq ! ( span. move_left( 5 ) , Span :: new( 0 , 5 ) ) ;
782+ }
783+
784+ #[ test]
785+ #[ should_panic( expected = "Cannot move span past zero length" ) ]
786+ fn test_move_past_start ( ) {
787+ let span = Span :: new ( 5 , 10 ) ;
788+ let _ = span. move_left ( 6 ) ;
789+ }
790+
791+ #[ test]
792+ fn test_move_right ( ) {
793+ let span: Span = Span :: new ( 5 , 10 ) ;
794+ assert_eq ! ( span. move_right( 1 ) , Span :: new( 6 , 11 ) ) ;
795+ assert_eq ! ( span. move_right( 2 ) , Span :: new( 7 , 12 ) ) ;
796+ assert_eq ! (
797+ span. move_right( u32 :: MAX . saturating_sub( 10 ) ) ,
798+ Span :: new( u32 :: MAX . saturating_sub( 5 ) , u32 :: MAX )
799+ ) ;
800+ }
801+
802+ #[ test]
803+ #[ should_panic( expected = "Cannot move span past `u32::MAX` length" ) ]
804+ fn test_move_past_end ( ) {
805+ let span = Span :: new ( u32:: MAX . saturating_sub ( 2 ) , u32:: MAX . saturating_sub ( 1 ) ) ;
806+ let _ = span. move_right ( 2 ) ;
807+ }
718808}
719809
720810#[ cfg( test) ]
0 commit comments