1use std::fmt;
4use std::num::NonZeroU32;
5use std::ops::Range;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14#[repr(transparent)]
15pub struct SourceId(NonZeroU32);
16
17impl SourceId {
18 pub const fn get(self) -> u32 {
20 self.0.get()
21 }
22
23 fn for_index(index: usize) -> Result<Self, SourceRangeError> {
24 let value = index
25 .checked_add(1)
26 .and_then(|value| u32::try_from(value).ok())
27 .and_then(NonZeroU32::new)
28 .ok_or(SourceRangeError::TooManySources)?;
29 Ok(Self(value))
30 }
31
32 fn index(self) -> usize {
33 usize::try_from(self.0.get() - 1).expect("u32 source ID fits in usize")
34 }
35}
36
37#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
43#[repr(transparent)]
44pub struct ByteOffset(u32);
45
46impl ByteOffset {
47 pub const fn new(value: u32) -> Self {
49 Self(value)
50 }
51
52 pub const fn get(self) -> u32 {
54 self.0
55 }
56
57 pub const fn as_usize(self) -> usize {
59 self.0 as usize
60 }
61}
62
63impl TryFrom<usize> for ByteOffset {
64 type Error = SourceRangeError;
65
66 fn try_from(offset: usize) -> Result<Self, Self::Error> {
67 u32::try_from(offset)
68 .map(Self)
69 .map_err(|_| SourceRangeError::UnrepresentableOffset {
70 offset,
71 max: u32::MAX as usize,
72 })
73 }
74}
75
76impl fmt::Display for ByteOffset {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 self.0.fmt(formatter)
79 }
80}
81
82#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
88pub struct BytePosition {
89 line: u32,
90 column: u32,
91}
92
93impl BytePosition {
94 pub const fn new(line: u32, column: u32) -> Self {
96 Self { line, column }
97 }
98
99 pub const fn line(self) -> u32 {
101 self.line
102 }
103
104 pub const fn column(self) -> u32 {
106 self.column
107 }
108}
109
110#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
116pub struct Position {
117 line: u32,
118 character: u32,
119}
120
121impl Position {
122 pub const fn new(line: u32, character: u32) -> Self {
124 Self { line, character }
125 }
126
127 pub const fn line(self) -> u32 {
129 self.line
130 }
131
132 pub const fn character(self) -> u32 {
134 self.character
135 }
136}
137
138#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
140pub enum PositionEncoding {
141 Utf8,
143 Utf16,
145 Utf32,
147}
148
149impl fmt::Display for PositionEncoding {
150 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151 formatter.write_str(match self {
152 Self::Utf8 => "UTF-8",
153 Self::Utf16 => "UTF-16",
154 Self::Utf32 => "UTF-32",
155 })
156 }
157}
158
159#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
165pub struct SourceRange {
166 source: SourceId,
167 start: ByteOffset,
168 end: ByteOffset,
169}
170
171impl SourceRange {
172 pub const fn source(self) -> SourceId {
174 self.source
175 }
176
177 pub const fn start(self) -> ByteOffset {
179 self.start
180 }
181
182 pub const fn end(self) -> ByteOffset {
184 self.end
185 }
186
187 pub fn byte_range(self) -> Range<usize> {
189 self.start.as_usize()..self.end.as_usize()
190 }
191
192 pub fn cover(first: Self, last: Self) -> Result<Self, SourceRangeError> {
196 if first.source != last.source {
197 return Err(SourceRangeError::SourceMismatch {
198 expected: first.source,
199 actual: last.source,
200 });
201 }
202 if first.start > first.end {
203 return Err(SourceRangeError::ReversedRange {
204 start: first.start,
205 end: first.end,
206 });
207 }
208 if last.start > last.end {
209 return Err(SourceRangeError::ReversedRange {
210 start: last.start,
211 end: last.end,
212 });
213 }
214
215 Ok(Self {
216 source: first.source,
217 start: first.start.min(last.start),
218 end: first.end.max(last.end),
219 })
220 }
221}
222
223impl fmt::Display for SourceId {
224 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
225 self.0.fmt(formatter)
226 }
227}
228
229#[derive(Clone, Debug, Eq, Hash, PartialEq)]
231pub enum SourceOrigin {
232 File { path: PathBuf },
234 Embedded { identity: Arc<str> },
236 Memory { identity: Arc<str> },
238 Custom {
240 provider: Arc<str>,
241 identity: Arc<str>,
242 },
243}
244
245impl SourceOrigin {
246 pub fn file(path: impl Into<PathBuf>) -> Self {
248 Self::File { path: path.into() }
249 }
250
251 pub fn embedded(identity: impl Into<Arc<str>>) -> Self {
253 Self::Embedded {
254 identity: identity.into(),
255 }
256 }
257
258 pub fn memory(identity: impl Into<Arc<str>>) -> Self {
260 Self::Memory {
261 identity: identity.into(),
262 }
263 }
264
265 pub fn custom(provider: impl Into<Arc<str>>, identity: impl Into<Arc<str>>) -> Self {
267 Self::Custom {
268 provider: provider.into(),
269 identity: identity.into(),
270 }
271 }
272}
273
274#[derive(Debug)]
280pub(crate) struct LineIndex {
281 starts: Box<[usize]>,
282}
283
284impl LineIndex {
285 fn new(bytes: &[u8]) -> Self {
286 let mut starts = Vec::new();
287 starts.push(0);
288 let mut index = 0;
289 while index < bytes.len() {
290 match bytes[index] {
291 b'\r' if bytes.get(index + 1) == Some(&b'\n') => {
292 index += 2;
293 starts.push(index);
294 }
295 b'\r' | b'\n' => {
296 index += 1;
297 starts.push(index);
298 }
299 _ => index += 1,
300 }
301 }
302 Self {
303 starts: starts.into_boxed_slice(),
304 }
305 }
306
307 pub(crate) fn line_count(&self) -> usize {
308 self.starts.len()
309 }
310
311 pub(crate) fn line_start(&self, line_index: usize) -> Option<usize> {
312 self.starts.get(line_index).copied()
313 }
314
315 pub(crate) fn line_starts(&self) -> &[usize] {
316 &self.starts
317 }
318}
319
320#[derive(Debug)]
322pub struct SourceDocument {
323 id: SourceId,
324 origin: SourceOrigin,
325 label: Arc<str>,
326 bytes: Arc<[u8]>,
327 line_index: LineIndex,
328}
329
330impl SourceDocument {
331 pub fn id(&self) -> SourceId {
333 self.id
334 }
335
336 pub fn origin(&self) -> &SourceOrigin {
338 &self.origin
339 }
340
341 pub fn label(&self) -> &str {
343 &self.label
344 }
345
346 pub fn bytes(&self) -> &[u8] {
348 &self.bytes
349 }
350
351 pub fn len(&self) -> ByteOffset {
353 ByteOffset(u32::try_from(self.bytes.len()).expect("source length was validated"))
354 }
355
356 pub fn is_empty(&self) -> bool {
358 self.bytes.is_empty()
359 }
360
361 pub fn line_count(&self) -> usize {
366 self.line_index.starts.len()
367 }
368
369 pub fn offset(&self, offset: usize) -> Result<ByteOffset, SourceRangeError> {
373 let offset = ByteOffset::try_from(offset)?;
374 if offset > self.len() {
375 return Err(SourceRangeError::OffsetOutOfBounds {
376 offset,
377 len: self.len(),
378 });
379 }
380 Ok(offset)
381 }
382
383 pub fn range(&self, range: Range<usize>) -> Result<SourceRange, SourceRangeError> {
385 let start = ByteOffset::try_from(range.start)?;
386 let end = ByteOffset::try_from(range.end)?;
387 if start > end {
388 return Err(SourceRangeError::ReversedRange { start, end });
389 }
390 if end > self.len() {
391 return Err(SourceRangeError::OffsetOutOfBounds {
392 offset: end,
393 len: self.len(),
394 });
395 }
396 Ok(SourceRange {
397 source: self.id,
398 start,
399 end,
400 })
401 }
402
403 pub fn empty_range(&self, offset: usize) -> Result<SourceRange, SourceRangeError> {
405 self.range(offset..offset)
406 }
407
408 pub fn slice(&self, range: SourceRange) -> Result<&[u8], SourceRangeError> {
410 if range.source != self.id {
411 return Err(SourceRangeError::SourceMismatch {
412 expected: self.id,
413 actual: range.source,
414 });
415 }
416 if range.start > range.end {
417 return Err(SourceRangeError::ReversedRange {
418 start: range.start,
419 end: range.end,
420 });
421 }
422 if range.end > self.len() {
423 return Err(SourceRangeError::OffsetOutOfBounds {
424 offset: range.end,
425 len: self.len(),
426 });
427 }
428 Ok(&self.bytes[range.byte_range()])
429 }
430
431 pub(crate) fn line_index(&self) -> &LineIndex {
432 &self.line_index
433 }
434
435 pub fn line_column(&self, offset: ByteOffset) -> Result<(usize, usize), SourceRangeError> {
440 if offset > self.len() {
441 return Err(SourceRangeError::OffsetOutOfBounds {
442 offset,
443 len: self.len(),
444 });
445 }
446 let offset = offset.as_usize();
447 let line_index = self
448 .line_index
449 .line_starts()
450 .partition_point(|&start| start <= offset)
451 .saturating_sub(1);
452 Ok((
453 line_index + 1,
454 offset - self.line_index.line_starts()[line_index] + 1,
455 ))
456 }
457
458 pub fn byte_position(&self, offset: ByteOffset) -> Result<BytePosition, PositionError> {
463 if offset > self.len() {
464 return Err(PositionError::OffsetOutOfBounds {
465 offset,
466 len: self.len(),
467 });
468 }
469 let offset = offset.as_usize();
470 let line = self
471 .line_index
472 .starts
473 .partition_point(|&start| start <= offset)
474 .saturating_sub(1);
475 let column = offset - self.line_index.starts[line];
476 Ok(BytePosition::new(
477 u32::try_from(line).expect("source line index fits in u32"),
478 u32::try_from(column).expect("source byte column fits in u32"),
479 ))
480 }
481
482 pub fn byte_offset(&self, position: BytePosition) -> Result<ByteOffset, PositionError> {
488 let line = position.line as usize;
489 let Some(&start) = self.line_index.starts.get(line) else {
490 return Err(PositionError::LineOutOfBounds {
491 line: position.line,
492 line_count: self.line_count(),
493 });
494 };
495 let is_final = line + 1 == self.line_count();
496 let full_end = self
497 .line_index
498 .starts
499 .get(line + 1)
500 .copied()
501 .unwrap_or_else(|| self.bytes.len());
502 let max_column = full_end - start - usize::from(!is_final);
503 if position.column as usize > max_column {
504 return Err(PositionError::ByteColumnOutOfBounds {
505 line: position.line,
506 column: position.column,
507 max_column: u32::try_from(max_column).expect("source byte column fits in u32"),
508 });
509 }
510 Ok(ByteOffset::new(
511 u32::try_from(start + position.column as usize)
512 .expect("validated source offset fits in u32"),
513 ))
514 }
515
516 pub fn position(
523 &self,
524 offset: ByteOffset,
525 encoding: PositionEncoding,
526 ) -> Result<Position, PositionError> {
527 let byte_position = self.byte_position(offset)?;
528 let text = self.valid_utf8()?;
529 let extent = self
530 .line_extent(byte_position.line)
531 .expect("validated byte position identifies a line");
532 let offset = offset.as_usize();
533 if offset > extent.content_end {
534 return Err(PositionError::OffsetInsideLineTerminator {
535 offset: ByteOffset::new(
536 u32::try_from(offset).expect("validated source offset fits in u32"),
537 ),
538 line: byte_position.line,
539 });
540 }
541 if !text.is_char_boundary(offset) {
542 return Err(PositionError::MidCodePoint {
543 offset: ByteOffset::new(
544 u32::try_from(offset).expect("validated source offset fits in u32"),
545 ),
546 });
547 }
548 let prefix = &text[extent.start..offset];
549 let character = match encoding {
550 PositionEncoding::Utf8 => prefix.len(),
551 PositionEncoding::Utf16 => prefix.encode_utf16().count(),
552 PositionEncoding::Utf32 => prefix.chars().count(),
553 };
554 Ok(Position::new(
555 byte_position.line,
556 u32::try_from(character).expect("encoded source column fits in u32"),
557 ))
558 }
559
560 pub fn position_offset(
566 &self,
567 position: Position,
568 encoding: PositionEncoding,
569 ) -> Result<ByteOffset, PositionError> {
570 let Some(extent) = self.line_extent(position.line) else {
571 return Err(PositionError::LineOutOfBounds {
572 line: position.line,
573 line_count: self.line_count(),
574 });
575 };
576 let text = self.valid_utf8()?;
577 let line_text = &text[extent.start..extent.content_end];
578 let character = position.character as usize;
579 let relative_offset = match encoding {
580 PositionEncoding::Utf8 => {
581 if character > line_text.len() {
582 return Err(PositionError::CharacterOutOfBounds {
583 line: position.line,
584 character: position.character,
585 max_character: u32::try_from(line_text.len())
586 .expect("encoded source column fits in u32"),
587 encoding,
588 });
589 }
590 if !line_text.is_char_boundary(character) {
591 return Err(PositionError::MidCodePoint {
592 offset: ByteOffset::new(
593 u32::try_from(extent.start + character)
594 .expect("validated source offset fits in u32"),
595 ),
596 });
597 }
598 character
599 }
600 PositionEncoding::Utf16 => {
601 let mut units = 0usize;
602 let mut result = None;
603 for (byte_index, value) in line_text.char_indices() {
604 if units == character {
605 result = Some(byte_index);
606 break;
607 }
608 let next = units + value.len_utf16();
609 if character < next {
610 return Err(PositionError::MidUtf16Surrogate {
611 line: position.line,
612 character: position.character,
613 });
614 }
615 units = next;
616 }
617 if result.is_none() && units == character {
618 result = Some(line_text.len());
619 }
620 match result {
621 Some(offset) => offset,
622 None => {
623 return Err(PositionError::CharacterOutOfBounds {
624 line: position.line,
625 character: position.character,
626 max_character: u32::try_from(units)
627 .expect("encoded source column fits in u32"),
628 encoding,
629 });
630 }
631 }
632 }
633 PositionEncoding::Utf32 => {
634 let count = line_text.chars().count();
635 if character > count {
636 return Err(PositionError::CharacterOutOfBounds {
637 line: position.line,
638 character: position.character,
639 max_character: u32::try_from(count)
640 .expect("encoded source column fits in u32"),
641 encoding,
642 });
643 }
644 line_text
645 .char_indices()
646 .map(|(byte_index, _)| byte_index)
647 .chain(std::iter::once(line_text.len()))
648 .nth(character)
649 .expect("validated UTF-32 column has a byte boundary")
650 }
651 };
652 Ok(ByteOffset::new(
653 u32::try_from(extent.start + relative_offset)
654 .expect("validated source offset fits in u32"),
655 ))
656 }
657
658 fn valid_utf8(&self) -> Result<&str, PositionError> {
659 std::str::from_utf8(&self.bytes).map_err(|error| PositionError::InvalidUtf8 {
660 valid_up_to: ByteOffset::new(
661 u32::try_from(error.valid_up_to()).expect("source offset fits in u32"),
662 ),
663 error_len: error.error_len(),
664 })
665 }
666
667 fn line_extent(&self, line: u32) -> Option<LineExtent> {
668 let line = line as usize;
669 let &start = self.line_index.starts.get(line)?;
670 let full_end = self
671 .line_index
672 .starts
673 .get(line + 1)
674 .copied()
675 .unwrap_or_else(|| self.bytes.len());
676 let mut content_end = full_end;
677 if line + 1 < self.line_count() {
678 match self.bytes[full_end - 1] {
679 b'\n' => {
680 content_end -= 1;
681 if content_end > start && self.bytes[content_end - 1] == b'\r' {
682 content_end -= 1;
683 }
684 }
685 b'\r' => content_end -= 1,
686 _ => unreachable!("line index ends non-final lines after terminators"),
687 }
688 }
689 Some(LineExtent { start, content_end })
690 }
691}
692
693#[derive(Clone, Copy, Debug)]
694struct LineExtent {
695 start: usize,
696 content_end: usize,
697}
698
699#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
701pub enum SourceRangeError {
702 #[error("byte offset {offset} cannot be represented (maximum is {max})")]
704 UnrepresentableOffset { offset: usize, max: usize },
705 #[error("byte offset {offset} is outside a source of length {len}")]
707 OffsetOutOfBounds { offset: ByteOffset, len: ByteOffset },
708 #[error("source range start {start} follows end {end}")]
710 ReversedRange { start: ByteOffset, end: ByteOffset },
711 #[error("source range belongs to source {actual}, not source {expected}")]
713 SourceMismatch {
714 expected: SourceId,
715 actual: SourceId,
716 },
717 #[error("source is too large ({len} bytes; maximum is {max})")]
719 SourceTooLarge { len: usize, max: usize },
720 #[error("too many sources in one compilation")]
722 TooManySources,
723}
724
725#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
727pub enum PositionError {
728 #[error("byte offset {offset} is outside a source of length {len}")]
730 OffsetOutOfBounds { offset: ByteOffset, len: ByteOffset },
731 #[error("line {line} is outside a source with {line_count} lines")]
733 LineOutOfBounds { line: u32, line_count: usize },
734 #[error("byte column {column} on line {line} exceeds maximum {max_column}")]
736 ByteColumnOutOfBounds {
737 line: u32,
738 column: u32,
739 max_column: u32,
740 },
741 #[error("{encoding} character {character} on line {line} exceeds maximum {max_character}")]
743 CharacterOutOfBounds {
744 line: u32,
745 character: u32,
746 max_character: u32,
747 encoding: PositionEncoding,
748 },
749 #[error("byte offset {offset} falls inside the line {line} terminator")]
751 OffsetInsideLineTerminator { offset: ByteOffset, line: u32 },
752 #[error("byte offset {offset} falls inside a UTF-8 code point")]
754 MidCodePoint { offset: ByteOffset },
755 #[error("UTF-16 character {character} on line {line} falls inside a surrogate pair")]
757 MidUtf16Surrogate { line: u32, character: u32 },
758 #[error("source is not valid UTF-8 after byte {valid_up_to} (invalid length {error_len:?})")]
760 InvalidUtf8 {
761 valid_up_to: ByteOffset,
762 error_len: Option<usize>,
763 },
764}
765
766#[derive(Debug, Default)]
779pub struct SourceSet {
780 documents: Vec<Arc<SourceDocument>>,
781}
782
783impl SourceSet {
784 pub fn new() -> Self {
786 Self::default()
787 }
788
789 pub fn len(&self) -> usize {
791 self.documents.len()
792 }
793
794 pub fn is_empty(&self) -> bool {
796 self.documents.is_empty()
797 }
798
799 pub fn insert(
801 &mut self,
802 origin: SourceOrigin,
803 label: impl Into<Arc<str>>,
804 bytes: Arc<[u8]>,
805 ) -> Result<SourceId, SourceRangeError> {
806 self.insert_shared(origin, label, bytes)
807 .map(|document| document.id())
808 }
809
810 pub(crate) fn insert_shared(
811 &mut self,
812 origin: SourceOrigin,
813 label: impl Into<Arc<str>>,
814 bytes: Arc<[u8]>,
815 ) -> Result<Arc<SourceDocument>, SourceRangeError> {
816 validate_source_len(bytes.len())?;
817 let id = SourceId::for_index(self.documents.len())?;
818 let document = Arc::new(SourceDocument {
819 id,
820 origin,
821 label: label.into(),
822 line_index: LineIndex::new(&bytes),
823 bytes,
824 });
825 self.documents.push(Arc::clone(&document));
826 Ok(document)
827 }
828
829 pub fn get(&self, id: SourceId) -> Option<&SourceDocument> {
831 self.documents.get(id.index()).map(Arc::as_ref)
832 }
833
834 pub fn iter(&self) -> impl ExactSizeIterator<Item = &SourceDocument> {
836 self.documents.iter().map(Arc::as_ref)
837 }
838}
839
840fn validate_source_len(len: usize) -> Result<(), SourceRangeError> {
841 let max = u32::MAX as usize;
842 if len > max {
843 return Err(SourceRangeError::SourceTooLarge { len, max });
844 }
845 Ok(())
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851
852 fn memory_origin(identity: &str) -> SourceOrigin {
853 SourceOrigin::Memory {
854 identity: Arc::from(identity),
855 }
856 }
857
858 fn test_document(bytes: &[u8]) -> Arc<SourceDocument> {
859 let mut sources = SourceSet::new();
860 sources
861 .insert_shared(
862 memory_origin("position-test"),
863 "position-test",
864 Arc::from(bytes),
865 )
866 .unwrap()
867 }
868
869 #[test]
870 fn source_set_allocates_nonzero_unique_ids() {
871 let mut sources = SourceSet::new();
872 let first = sources
873 .insert(memory_origin("buffer:one"), "one", Arc::from(&b"one"[..]))
874 .unwrap();
875 let second = sources
876 .insert(memory_origin("buffer:two"), "two", Arc::from(&b"two"[..]))
877 .unwrap();
878
879 assert_ne!(first, second);
880 assert_eq!(first.get(), 1);
881 assert_eq!(second.get(), 2);
882 assert_eq!(first.to_string(), "1");
883 assert_eq!(second.to_string(), "2");
884 assert_eq!(sources.len(), 2);
885 assert!(!sources.is_empty());
886 assert_eq!(
887 sources.iter().map(SourceDocument::id).collect::<Vec<_>>(),
888 [first, second]
889 );
890 }
891
892 #[test]
893 fn origin_identity_is_distinct_from_display_label() {
894 let mut sources = SourceSet::new();
895 let origin = SourceOrigin::Custom {
896 provider: Arc::from("workspace"),
897 identity: Arc::from("document/42"),
898 };
899 let id = sources
900 .insert(origin.clone(), "ACME-MIB", Arc::from(&b"contents"[..]))
901 .unwrap();
902 let document = sources.get(id).unwrap();
903
904 assert_eq!(document.origin(), &origin);
905 assert_eq!(document.label(), "ACME-MIB");
906 assert_ne!(document.label(), "document/42");
907 }
908
909 #[test]
910 fn document_retains_shared_bytes_without_copying() {
911 let bytes: Arc<[u8]> = Arc::from(&b"first\nsecond"[..]);
912 let mut sources = SourceSet::new();
913 let id = sources
914 .insert(memory_origin("buffer"), "buffer", Arc::clone(&bytes))
915 .unwrap();
916 let document = sources.get(id).unwrap();
917
918 assert_eq!(document.bytes(), bytes.as_ref());
919 assert_eq!(document.bytes().as_ptr(), bytes.as_ptr());
920 assert_eq!(Arc::strong_count(&bytes), 2);
921 }
922
923 #[test]
924 fn source_lookup_checks_id_bounds() {
925 let mut sources = SourceSet::new();
926 let id = sources
927 .insert(memory_origin("buffer"), "buffer", Arc::from(&b""[..]))
928 .unwrap();
929
930 assert_eq!(sources.get(id).unwrap().id(), id);
931 assert!(sources.get(SourceId::for_index(1).unwrap()).is_none());
932 }
933
934 #[test]
935 fn line_index_owns_all_line_starts_and_checks_bounds() {
936 let mut sources = SourceSet::new();
937 let id = sources
938 .insert(
939 memory_origin("buffer"),
940 "buffer",
941 Arc::from(&b"first\n\nthird\n"[..]),
942 )
943 .unwrap();
944 let document = sources.get(id).unwrap();
945 let index = document.line_index();
946
947 assert_eq!(index.line_starts(), &[0, 6, 7, 13]);
948 assert_eq!(index.line_count(), 4);
949 assert_eq!(index.line_start(0), Some(0));
950 assert_eq!(index.line_start(3), Some(13));
951 assert_eq!(index.line_start(4), None);
952 }
953
954 #[test]
955 fn document_converts_checked_byte_offsets_to_one_based_positions() {
956 let mut sources = SourceSet::new();
957 let id = sources
958 .insert(
959 memory_origin("buffer"),
960 "buffer",
961 Arc::from(&b"first\nsecond"[..]),
962 )
963 .unwrap();
964 let document = sources.get(id).unwrap();
965
966 assert_eq!(document.line_column(ByteOffset::new(0)).unwrap(), (1, 1));
967 assert_eq!(document.line_column(ByteOffset::new(6)).unwrap(), (2, 1));
968 assert_eq!(document.line_column(document.len()).unwrap(), (2, 7));
969 assert_eq!(
970 document.line_column(ByteOffset::new(13)),
971 Err(SourceRangeError::OffsetOutOfBounds {
972 offset: ByteOffset::new(13),
973 len: document.len(),
974 })
975 );
976 }
977
978 #[test]
979 fn all_origin_kinds_retain_typed_identity() {
980 let origins = [
981 SourceOrigin::File {
982 path: PathBuf::from("/mibs/IF-MIB"),
983 },
984 SourceOrigin::Embedded {
985 identity: Arc::from("SNMPv2-SMI"),
986 },
987 memory_origin("untitled:1"),
988 SourceOrigin::Custom {
989 provider: Arc::from("database"),
990 identity: Arc::from("mib/7"),
991 },
992 ];
993
994 assert_eq!(origins.len(), 4);
995 assert!(origins.iter().all(|origin| origins.contains(origin)));
996 }
997
998 #[test]
999 fn rejects_unrepresentable_source_lengths_and_id_overflow() {
1000 let too_large = (u32::MAX as usize).checked_add(1).unwrap();
1001 assert_eq!(
1002 validate_source_len(too_large),
1003 Err(SourceRangeError::SourceTooLarge {
1004 len: too_large,
1005 max: u32::MAX as usize,
1006 })
1007 );
1008 assert_eq!(
1009 SourceId::for_index(u32::MAX as usize),
1010 Err(SourceRangeError::TooManySources)
1011 );
1012 let maximum = ByteOffset::try_from(u32::MAX as usize).unwrap();
1013 assert_eq!(maximum, ByteOffset::new(u32::MAX));
1014 assert_eq!(maximum.get(), u32::MAX);
1015 assert_eq!(maximum.as_usize(), u32::MAX as usize);
1016 }
1017
1018 #[test]
1019 fn empty_document_accepts_only_its_eof_offset_and_range() {
1020 let mut sources = SourceSet::new();
1021 let id = sources
1022 .insert(memory_origin("empty"), "empty", Arc::from(&b""[..]))
1023 .unwrap();
1024 let document = sources.get(id).unwrap();
1025
1026 assert!(document.is_empty());
1027 assert_eq!(document.len().get(), 0);
1028 assert_eq!(document.offset(0).unwrap().get(), 0);
1029 let range = document.empty_range(0).unwrap();
1030 assert_eq!(range.source(), id);
1031 assert_eq!(range.start().get(), 0);
1032 assert_eq!(range.end().get(), 0);
1033 assert_eq!(range.byte_range(), 0..0);
1034 assert_eq!(document.slice(range).unwrap(), b"");
1035 assert_eq!(
1036 document.offset(1),
1037 Err(SourceRangeError::OffsetOutOfBounds {
1038 offset: ByteOffset(1),
1039 len: ByteOffset(0),
1040 })
1041 );
1042 }
1043
1044 #[test]
1045 fn document_ranges_include_eof_and_slice_bytes() {
1046 let mut sources = SourceSet::new();
1047 let id = sources
1048 .insert(memory_origin("buffer"), "buffer", Arc::from(&b"abcdef"[..]))
1049 .unwrap();
1050 let document = sources.get(id).unwrap();
1051
1052 assert_eq!(document.len().get(), 6);
1053 assert_eq!(document.offset(6).unwrap().get(), 6);
1054 assert_eq!(
1055 document.slice(document.range(1..4).unwrap()).unwrap(),
1056 b"bcd"
1057 );
1058 assert_eq!(
1059 document.slice(document.empty_range(6).unwrap()).unwrap(),
1060 b""
1061 );
1062 }
1063
1064 #[test]
1065 fn document_rejects_reversed_and_out_of_bounds_ranges() {
1066 let mut sources = SourceSet::new();
1067 let id = sources
1068 .insert(memory_origin("buffer"), "buffer", Arc::from(&b"abcd"[..]))
1069 .unwrap();
1070 let document = sources.get(id).unwrap();
1071 let reversed_start = 3;
1072 let reversed_end = 2;
1073 let out_of_bounds_start = document.bytes().len() + 1;
1074
1075 assert_eq!(
1076 document.range(reversed_start..reversed_end),
1077 Err(SourceRangeError::ReversedRange {
1078 start: ByteOffset(3),
1079 end: ByteOffset(2),
1080 })
1081 );
1082 assert_eq!(
1083 document.range(0..5),
1084 Err(SourceRangeError::OffsetOutOfBounds {
1085 offset: ByteOffset(5),
1086 len: ByteOffset(4),
1087 })
1088 );
1089 assert_eq!(
1090 document.range(out_of_bounds_start..0),
1091 Err(SourceRangeError::ReversedRange {
1092 start: ByteOffset(5),
1093 end: ByteOffset(0),
1094 })
1095 );
1096 }
1097
1098 #[test]
1099 fn ranges_reject_cross_source_cover_and_slice() {
1100 let mut sources = SourceSet::new();
1101 let first_id = sources
1102 .insert(memory_origin("first"), "first", Arc::from(&b"first"[..]))
1103 .unwrap();
1104 let second_id = sources
1105 .insert(memory_origin("second"), "second", Arc::from(&b"second"[..]))
1106 .unwrap();
1107 let first = sources.get(first_id).unwrap().range(1..3).unwrap();
1108 let second = sources.get(second_id).unwrap().range(2..4).unwrap();
1109
1110 assert_eq!(
1111 SourceRange::cover(first, second),
1112 Err(SourceRangeError::SourceMismatch {
1113 expected: first_id,
1114 actual: second_id,
1115 })
1116 );
1117 assert_eq!(
1118 sources.get(first_id).unwrap().slice(second),
1119 Err(SourceRangeError::SourceMismatch {
1120 expected: first_id,
1121 actual: second_id,
1122 })
1123 );
1124 }
1125
1126 #[test]
1127 fn cover_spans_ordered_disjoint_and_nested_ranges() {
1128 let mut sources = SourceSet::new();
1129 let id = sources
1130 .insert(
1131 memory_origin("buffer"),
1132 "buffer",
1133 Arc::from(&b"0123456789"[..]),
1134 )
1135 .unwrap();
1136 let document = sources.get(id).unwrap();
1137 let left = document.range(1..3).unwrap();
1138 let right = document.range(7..9).unwrap();
1139 let nested = document.range(2..8).unwrap();
1140
1141 assert_eq!(
1142 SourceRange::cover(left, right).unwrap(),
1143 document.range(1..9).unwrap()
1144 );
1145 assert_eq!(
1146 SourceRange::cover(right, left).unwrap(),
1147 document.range(1..9).unwrap()
1148 );
1149 assert_eq!(
1150 SourceRange::cover(nested, left).unwrap(),
1151 document.range(1..8).unwrap()
1152 );
1153 }
1154
1155 #[test]
1156 fn byte_positions_round_trip_every_offset_for_arbitrary_bytes() {
1157 let cases: &[&[u8]] = &[
1158 b"",
1159 b"a",
1160 b"a\n",
1161 b"\n",
1162 b"a\r\nb",
1163 b"a\rb\n",
1164 &[0x00, 0xff, b'\r', b'\n', 0x80],
1165 ];
1166
1167 for &bytes in cases {
1168 let document = test_document(bytes);
1169 for raw_offset in 0..=bytes.len() {
1170 let offset = document.offset(raw_offset).unwrap();
1171 let position = document.byte_position(offset).unwrap();
1172 assert_eq!(
1173 document.byte_offset(position).unwrap(),
1174 offset,
1175 "bytes={bytes:?}, offset={raw_offset}, position={position:?}"
1176 );
1177 assert_eq!(
1178 document
1179 .byte_position(document.byte_offset(position).unwrap())
1180 .unwrap(),
1181 position
1182 );
1183 }
1184 }
1185 }
1186
1187 #[test]
1188 fn byte_positions_cover_empty_eof_trailing_newline_and_crlf_bytes() {
1189 let empty = test_document(b"");
1190 assert_eq!(empty.line_count(), 1);
1191 assert_eq!(
1192 empty.byte_position(ByteOffset::new(0)).unwrap(),
1193 BytePosition::new(0, 0)
1194 );
1195 assert_eq!(
1196 empty.byte_offset(BytePosition::new(0, 0)).unwrap(),
1197 ByteOffset::new(0)
1198 );
1199
1200 let document = test_document(b"a\r\nb\rc\n");
1201 assert_eq!(document.line_count(), 4);
1202 let expected = [
1203 BytePosition::new(0, 0),
1204 BytePosition::new(0, 1),
1205 BytePosition::new(0, 2),
1206 BytePosition::new(1, 0),
1207 BytePosition::new(1, 1),
1208 BytePosition::new(2, 0),
1209 BytePosition::new(2, 1),
1210 BytePosition::new(3, 0),
1211 ];
1212 for (offset, expected) in expected.into_iter().enumerate() {
1213 assert_eq!(
1214 document.byte_position(ByteOffset::new(offset as u32)),
1215 Ok(expected)
1216 );
1217 assert_eq!(
1218 document.byte_offset(expected),
1219 Ok(ByteOffset::new(offset as u32))
1220 );
1221 }
1222 assert_eq!(expected[2].line(), 0);
1223 assert_eq!(expected[2].column(), 2);
1224 }
1225
1226 #[test]
1227 fn byte_position_rejects_invalid_offset_line_and_column() {
1228 let document = test_document(b"a\nb");
1229
1230 assert_eq!(
1231 document.byte_position(ByteOffset::new(4)),
1232 Err(PositionError::OffsetOutOfBounds {
1233 offset: ByteOffset::new(4),
1234 len: ByteOffset::new(3),
1235 })
1236 );
1237 assert_eq!(
1238 document.byte_offset(BytePosition::new(2, 0)),
1239 Err(PositionError::LineOutOfBounds {
1240 line: 2,
1241 line_count: 2,
1242 })
1243 );
1244 assert_eq!(
1245 document.byte_offset(BytePosition::new(0, 2)),
1246 Err(PositionError::ByteColumnOutOfBounds {
1247 line: 0,
1248 column: 2,
1249 max_column: 1,
1250 })
1251 );
1252 assert_eq!(
1253 document.byte_offset(BytePosition::new(1, 2)),
1254 Err(PositionError::ByteColumnOutOfBounds {
1255 line: 1,
1256 column: 2,
1257 max_column: 1,
1258 })
1259 );
1260 }
1261
1262 #[test]
1263 fn editor_positions_use_explicit_utf_code_units() {
1264 let document = test_document("Aé𝄞".as_bytes());
1265 let cases = [
1266 (
1267 0,
1268 Position::new(0, 0),
1269 Position::new(0, 0),
1270 Position::new(0, 0),
1271 ),
1272 (
1273 1,
1274 Position::new(0, 1),
1275 Position::new(0, 1),
1276 Position::new(0, 1),
1277 ),
1278 (
1279 3,
1280 Position::new(0, 3),
1281 Position::new(0, 2),
1282 Position::new(0, 2),
1283 ),
1284 (
1285 7,
1286 Position::new(0, 7),
1287 Position::new(0, 4),
1288 Position::new(0, 3),
1289 ),
1290 ];
1291
1292 for (offset, utf8, utf16, utf32) in cases {
1293 let offset = ByteOffset::new(offset);
1294 assert_eq!(document.position(offset, PositionEncoding::Utf8), Ok(utf8));
1295 assert_eq!(
1296 document.position(offset, PositionEncoding::Utf16),
1297 Ok(utf16)
1298 );
1299 assert_eq!(
1300 document.position(offset, PositionEncoding::Utf32),
1301 Ok(utf32)
1302 );
1303 assert_eq!(
1304 document.position_offset(utf8, PositionEncoding::Utf8),
1305 Ok(offset)
1306 );
1307 assert_eq!(
1308 document.position_offset(utf16, PositionEncoding::Utf16),
1309 Ok(offset)
1310 );
1311 assert_eq!(
1312 document.position_offset(utf32, PositionEncoding::Utf32),
1313 Ok(offset)
1314 );
1315 }
1316
1317 assert_eq!(Position::new(2, 3).line(), 2);
1318 assert_eq!(Position::new(2, 3).character(), 3);
1319 assert_eq!(PositionEncoding::Utf8.to_string(), "UTF-8");
1320 assert_eq!(PositionEncoding::Utf16.to_string(), "UTF-16");
1321 assert_eq!(PositionEncoding::Utf32.to_string(), "UTF-32");
1322 }
1323
1324 #[test]
1325 fn editor_positions_follow_lsp_line_terminator_and_eof_semantics() {
1326 let document = test_document(b"a\r\nb\rc\n");
1327
1328 for encoding in [
1329 PositionEncoding::Utf8,
1330 PositionEncoding::Utf16,
1331 PositionEncoding::Utf32,
1332 ] {
1333 assert_eq!(
1334 document.position(ByteOffset::new(1), encoding),
1335 Ok(Position::new(0, 1))
1336 );
1337 assert_eq!(
1338 document.position(ByteOffset::new(2), encoding),
1339 Err(PositionError::OffsetInsideLineTerminator {
1340 offset: ByteOffset::new(2),
1341 line: 0,
1342 })
1343 );
1344 assert_eq!(
1345 document.position_offset(Position::new(0, 1), encoding),
1346 Ok(ByteOffset::new(1))
1347 );
1348 assert_eq!(
1349 document.position(ByteOffset::new(3), encoding),
1350 Ok(Position::new(1, 0))
1351 );
1352 assert_eq!(
1353 document.position(ByteOffset::new(4), encoding),
1354 Ok(Position::new(1, 1))
1355 );
1356 assert_eq!(
1357 document.position(ByteOffset::new(6), encoding),
1358 Ok(Position::new(2, 1))
1359 );
1360 assert_eq!(
1361 document.position(ByteOffset::new(7), encoding),
1362 Ok(Position::new(3, 0))
1363 );
1364 assert_eq!(
1365 document.position_offset(Position::new(3, 0), encoding),
1366 Ok(ByteOffset::new(7))
1367 );
1368 }
1369 }
1370
1371 #[test]
1372 fn editor_positions_round_trip_all_representable_offsets_and_positions() {
1373 let cases = ["", "plain", "trailing\n", "\r\n", "é\r\n𝄞\n", "a\rb"];
1374 for text in cases {
1375 let document = test_document(text.as_bytes());
1376 for encoding in [
1377 PositionEncoding::Utf8,
1378 PositionEncoding::Utf16,
1379 PositionEncoding::Utf32,
1380 ] {
1381 for raw_offset in 0..=text.len() {
1382 let offset = ByteOffset::new(raw_offset as u32);
1383 match document.position(offset, encoding) {
1384 Ok(position) => {
1385 assert_eq!(
1386 document.position_offset(position, encoding),
1387 Ok(offset),
1388 "text={text:?}, encoding={encoding}, offset={raw_offset}"
1389 );
1390 }
1391 Err(
1392 PositionError::MidCodePoint { .. }
1393 | PositionError::OffsetInsideLineTerminator { .. },
1394 ) => {}
1395 Err(error) => panic!(
1396 "unexpected conversion error for text={text:?}, encoding={encoding}, offset={raw_offset}: {error}"
1397 ),
1398 }
1399 }
1400
1401 for line in 0..document.line_count() {
1402 let extent = document.line_extent(line as u32).unwrap();
1403 let end = document
1404 .position(ByteOffset::new(extent.content_end as u32), encoding)
1405 .unwrap();
1406 for character in 0..=end.character() {
1407 let position = Position::new(line as u32, character);
1408 match document.position_offset(position, encoding) {
1409 Ok(offset) => assert_eq!(
1410 document.position(offset, encoding),
1411 Ok(position),
1412 "text={text:?}, encoding={encoding}, position={position:?}"
1413 ),
1414 Err(
1415 PositionError::MidCodePoint { .. }
1416 | PositionError::MidUtf16Surrogate { .. },
1417 ) => {}
1418 Err(error) => panic!(
1419 "unexpected inverse error for text={text:?}, encoding={encoding}, position={position:?}: {error}"
1420 ),
1421 }
1422 }
1423 }
1424 }
1425 }
1426 }
1427
1428 #[test]
1429 fn editor_positions_reject_mid_codepoint_mid_surrogate_and_bad_coordinates() {
1430 let document = test_document("Aé𝄞".as_bytes());
1431
1432 for encoding in [
1433 PositionEncoding::Utf8,
1434 PositionEncoding::Utf16,
1435 PositionEncoding::Utf32,
1436 ] {
1437 assert_eq!(
1438 document.position(ByteOffset::new(2), encoding),
1439 Err(PositionError::MidCodePoint {
1440 offset: ByteOffset::new(2),
1441 })
1442 );
1443 }
1444 assert_eq!(
1445 document.position_offset(Position::new(0, 2), PositionEncoding::Utf8),
1446 Err(PositionError::MidCodePoint {
1447 offset: ByteOffset::new(2),
1448 })
1449 );
1450 assert_eq!(
1451 document.position_offset(Position::new(0, 3), PositionEncoding::Utf16),
1452 Err(PositionError::MidUtf16Surrogate {
1453 line: 0,
1454 character: 3,
1455 })
1456 );
1457 assert_eq!(
1458 document.position_offset(Position::new(1, 0), PositionEncoding::Utf32),
1459 Err(PositionError::LineOutOfBounds {
1460 line: 1,
1461 line_count: 1,
1462 })
1463 );
1464 assert_eq!(
1465 document.position_offset(Position::new(0, 8), PositionEncoding::Utf8),
1466 Err(PositionError::CharacterOutOfBounds {
1467 line: 0,
1468 character: 8,
1469 max_character: 7,
1470 encoding: PositionEncoding::Utf8,
1471 })
1472 );
1473 assert_eq!(
1474 document.position_offset(Position::new(0, 5), PositionEncoding::Utf16),
1475 Err(PositionError::CharacterOutOfBounds {
1476 line: 0,
1477 character: 5,
1478 max_character: 4,
1479 encoding: PositionEncoding::Utf16,
1480 })
1481 );
1482 assert_eq!(
1483 document.position_offset(Position::new(0, 4), PositionEncoding::Utf32),
1484 Err(PositionError::CharacterOutOfBounds {
1485 line: 0,
1486 character: 4,
1487 max_character: 3,
1488 encoding: PositionEncoding::Utf32,
1489 })
1490 );
1491 assert_eq!(
1492 document.position(ByteOffset::new(8), PositionEncoding::Utf16),
1493 Err(PositionError::OffsetOutOfBounds {
1494 offset: ByteOffset::new(8),
1495 len: ByteOffset::new(7),
1496 })
1497 );
1498 }
1499
1500 #[test]
1501 fn invalid_utf8_retains_byte_positions_but_rejects_editor_positions() {
1502 let bytes = [b'a', 0xff, b'\n', 0x80];
1503 let document = test_document(&bytes);
1504
1505 for raw_offset in 0..=bytes.len() {
1506 let offset = ByteOffset::new(raw_offset as u32);
1507 let position = document.byte_position(offset).unwrap();
1508 assert_eq!(document.byte_offset(position), Ok(offset));
1509 }
1510 for encoding in [
1511 PositionEncoding::Utf8,
1512 PositionEncoding::Utf16,
1513 PositionEncoding::Utf32,
1514 ] {
1515 assert_eq!(
1516 document.position(ByteOffset::new(0), encoding),
1517 Err(PositionError::InvalidUtf8 {
1518 valid_up_to: ByteOffset::new(1),
1519 error_len: Some(1),
1520 })
1521 );
1522 assert_eq!(
1523 document.position_offset(Position::new(0, 0), encoding),
1524 Err(PositionError::InvalidUtf8 {
1525 valid_up_to: ByteOffset::new(1),
1526 error_len: Some(1),
1527 })
1528 );
1529 }
1530 assert_eq!(
1531 document.position(ByteOffset::new(5), PositionEncoding::Utf8),
1532 Err(PositionError::OffsetOutOfBounds {
1533 offset: ByteOffset::new(5),
1534 len: ByteOffset::new(4),
1535 })
1536 );
1537 assert_eq!(
1538 document.position_offset(Position::new(3, 0), PositionEncoding::Utf8),
1539 Err(PositionError::LineOutOfBounds {
1540 line: 3,
1541 line_count: 2,
1542 })
1543 );
1544 }
1545
1546 #[cfg(target_pointer_width = "64")]
1547 #[test]
1548 fn offsets_beyond_u32_are_reported_as_unrepresentable() {
1549 let mut sources = SourceSet::new();
1550 let id = sources
1551 .insert(memory_origin("buffer"), "buffer", Arc::from(&b"bytes"[..]))
1552 .unwrap();
1553 let document = sources.get(id).unwrap();
1554 let offset = u32::MAX as usize + 1;
1555
1556 assert_eq!(
1557 document.offset(offset),
1558 Err(SourceRangeError::UnrepresentableOffset {
1559 offset,
1560 max: u32::MAX as usize,
1561 })
1562 );
1563 }
1564}