From 129f91e444fbb8731adf3d3c83cdb5766be5fe54 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Sun, 9 Jun 2024 12:59:00 -0400 Subject: [PATCH] feat(span): port over more methods from TextRange (#3592) Ports over more utility methods from `TextRange`, as per feedback in [this comment](https://github.com/oxc-project/oxc/pull/3589#discussion_r1632169132) from #3589 --- crates/oxc_span/src/atom.rs | 18 +++- crates/oxc_span/src/span.rs | 175 +++++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 6 deletions(-) diff --git a/crates/oxc_span/src/atom.rs b/crates/oxc_span/src/atom.rs index ca4fd8846..405d88547 100644 --- a/crates/oxc_span/src/atom.rs +++ b/crates/oxc_span/src/atom.rs @@ -1,10 +1,16 @@ -use std::{borrow::Borrow, fmt, hash, ops::Deref}; +use std::{ + borrow::Borrow, + fmt, hash, + ops::{Deref, Index}, +}; #[cfg(feature = "serialize")] use serde::{Serialize, Serializer}; use compact_str::CompactString; +use crate::Span; + #[cfg(feature = "serialize")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] const TS_APPEND_CONTENT: &'static str = r#" @@ -12,7 +18,7 @@ export type Atom = string; export type CompactStr = string; "#; -/// Maximum length for inline string, which can be created with `CompactStr::new_const`. +/// Maximum length for inline string, which can be created with [`CompactStr::new_const`]. pub const MAX_INLINE_LEN: usize = 16; /// An inlinable string for oxc_allocator. @@ -261,6 +267,14 @@ impl PartialEq for &str { } } +impl Index for CompactStr { + type Output = str; + + fn index(&self, index: Span) -> &Self::Output { + &self.0[index] + } +} + impl hash::Hash for CompactStr { fn hash(&self, hasher: &mut H) { self.as_str().hash(hasher); diff --git a/crates/oxc_span/src/span.rs b/crates/oxc_span/src/span.rs index dc61803c8..7dd536a1a 100644 --- a/crates/oxc_span/src/span.rs +++ b/crates/oxc_span/src/span.rs @@ -1,7 +1,10 @@ -// Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` +// Silence erroneous warnings from Rust Analyzer for `#[derive(Tsify)]` #![allow(non_snake_case)] -use std::hash::{Hash, Hasher}; +use std::{ + hash::{Hash, Hasher}, + ops::{Index, IndexMut, Range}, +}; use miette::{LabeledSpan, SourceOffset, SourceSpan}; @@ -56,6 +59,35 @@ impl Span { Self { start, end } } + /// Create a new empty [`Span`] that starts and ends at an offset position. + /// + /// # Examples + /// ``` + /// use oxc_span::Span; + /// + /// let fifth = Span::empty(5); + /// assert!(fifth.is_empty()); + /// assert_eq!(fifth, Span::sized(5, 0)); + /// assert_eq!(fifth, Span::new(5, 5)); + /// ``` + pub fn empty(at: u32) -> Self { + Self { start: at, end: at } + } + + /// Create a new [`Span`] starting at `start` and covering `size` characters. + /// + /// # Example + /// ``` + /// use oxc_span::Span; + /// + /// let span = Span::sized(2, 4); + /// assert_eq!(span.size(), 4); + /// assert_eq!(span, Span::new(2, 6)); + /// ``` + pub const fn sized(start: u32, size: u32) -> Self { + Self::new(start, start + size) + } + /// Get the number of characters covered by the [`Span`]. /// /// # Example @@ -66,7 +98,7 @@ impl Span { /// assert_eq!(Span::new(0, 5).size(), 5); /// assert_eq!(Span::new(5, 10).size(), 5); /// ``` - pub fn size(&self) -> u32 { + pub const fn size(&self) -> u32 { debug_assert!(self.start <= self.end); self.end - self.start } @@ -81,7 +113,7 @@ impl Span { /// assert!(Span::new(5, 5).is_empty()); /// assert!(!Span::new(0, 5).is_empty()); /// ``` - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { debug_assert!(self.start <= self.end); self.start == self.end } @@ -102,6 +134,119 @@ impl Span { Self::new(self.start.min(other.start), self.end.max(other.end)) } + /// Create a [`Span`] that has its start position moved to the left by + /// `offset` bytes. + /// + /// # Example + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(5, 10); + /// assert_eq!(a.expand_left(5), Span::new(0, 10)); + /// ``` + /// + /// ## Bounds + /// + /// The leftmost bound of the span is clamped to 0. It is safe to call this + /// method with a value larger than the start position. + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(0, 5); + /// assert_eq!(a.expand_left(5), Span::new(0, 5)); + /// ``` + #[must_use] + pub const fn expand_left(self, offset: u32) -> Self { + Self::new(self.start.saturating_sub(offset), self.end) + } + + /// Create a [`Span`] that has its start position moved to the right by + /// `offset` bytes. + /// + /// It is a logical error to shrink the start of the [`Span`] past its end + /// position. + /// + /// # Example + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(5, 10); + /// let shrunk = a.shrink_left(5); + /// assert_eq!(shrunk, Span::new(10, 10)); + /// + /// // Shrinking past the end of the span is a logical error that will panic + /// // in debug builds. + /// std::panic::catch_unwind(|| { + /// shrunk.shrink_left(5); + /// }); + /// ``` + /// + #[must_use] + pub const fn shrink_left(self, offset: u32) -> Self { + let start = self.start.saturating_add(offset); + debug_assert!(start <= self.end); + Self::new(self.start.saturating_add(offset), self.end) + } + + /// Create a [`Span`] that has its end position moved to the right by + /// `offset` bytes. + /// + /// # Example + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(5, 10); + /// assert_eq!(a.expand_right(5), Span::new(5, 15)); + /// ``` + /// + /// ## Bounds + /// + /// The rightmost bound of the span is clamped to `u32::MAX`. It is safe to + /// call this method with a value larger than the end position. + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(0, u32::MAX); + /// assert_eq!(a.expand_right(5), Span::new(0, u32::MAX)); + /// ``` + #[must_use] + pub const fn expand_right(self, offset: u32) -> Self { + Self::new(self.start, self.end.saturating_add(offset)) + } + + /// Create a [`Span`] that has its end position moved to the left by + /// `offset` bytes. + /// + /// It is a logical error to shrink the end of the [`Span`] past its start + /// position. + /// + /// # Example + /// + /// ``` + /// use oxc_span::Span; + /// + /// let a = Span::new(5, 10); + /// let shrunk = a.shrink_right(5); + /// assert_eq!(shrunk, Span::new(5, 5)); + /// + /// // Shrinking past the start of the span is a logical error that will panic + /// // in debug builds. + /// std::panic::catch_unwind(|| { + /// shrunk.shrink_right(5); + /// }); + /// ``` + #[must_use] + pub const fn shrink_right(self, offset: u32) -> Self { + let end = self.end.saturating_sub(offset); + debug_assert!(self.start <= end); + Self::new(self.start, end) + } + /// Get a snippet of text from a source string that the [`Span`] covers. /// /// # Example @@ -118,6 +263,28 @@ impl Span { } } +impl Index for str { + type Output = str; + #[inline] + fn index(&self, index: Span) -> &Self::Output { + &self[index.start as usize..index.end as usize] + } +} + +impl IndexMut for str { + #[inline] + fn index_mut(&mut self, index: Span) -> &mut Self::Output { + &mut self[index.start as usize..index.end as usize] + } +} + +impl From> for Span { + #[inline] + fn from(range: Range) -> Self { + Self::new(range.start, range.end) + } +} + impl Hash for Span { fn hash(&self, _state: &mut H) { // hash to nothing so all ast spans can be comparable with hash