1use std::cmp::Ordering;
2use std::ops::{Deref, Range};
3use std::sync::Arc;
4
5#[cfg(feature = "stringify")]
6use serde::{Deserialize, Serialize};
7
8use smol_str::SmolStr;
9
10use crate::errors::SQLFluffSkipFile;
11use crate::slice_helpers::zero_slice;
12
13#[cfg_attr(feature = "stringify", derive(Serialize, Deserialize))]
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum TemplateSliceKind {
16 Literal,
17 Templated,
18 Comment,
19 BlockStart,
20 BlockMid,
21 BlockEnd,
22}
23
24impl TemplateSliceKind {
25 pub const fn as_str(self) -> &'static str {
26 match self {
27 Self::Literal => "literal",
28 Self::Templated => "templated",
29 Self::Comment => "comment",
30 Self::BlockStart => "block_start",
31 Self::BlockMid => "block_mid",
32 Self::BlockEnd => "block_end",
33 }
34 }
35
36 pub const fn is_source_only(self) -> bool {
37 matches!(
38 self,
39 Self::Comment | Self::BlockEnd | Self::BlockStart | Self::BlockMid
40 )
41 }
42
43 pub fn from_slice_type(value: &str) -> Result<Self, String> {
44 match value {
45 "literal" => Ok(Self::Literal),
46 "templated" => Ok(Self::Templated),
47 "comment" => Ok(Self::Comment),
48 "block_start" => Ok(Self::BlockStart),
49 "block_mid" => Ok(Self::BlockMid),
50 "block_end" => Ok(Self::BlockEnd),
51 _ => Err(format!("Unknown template slice kind '{value}'")),
52 }
53 }
54}
55
56#[cfg_attr(feature = "stringify", derive(Serialize))]
58#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub struct TemplatedFileSlice {
60 pub slice_type: TemplateSliceKind,
61 pub source_slice: Range<usize>,
62 pub templated_slice: Range<usize>,
63}
64
65impl TemplatedFileSlice {
66 pub fn new(
67 slice_type: TemplateSliceKind,
68 source_slice: Range<usize>,
69 templated_slice: Range<usize>,
70 ) -> Self {
71 Self {
72 slice_type,
73 source_slice,
74 templated_slice,
75 }
76 }
77
78 pub fn new_typed(
79 slice_type: TemplateSliceKind,
80 source_slice: Range<usize>,
81 templated_slice: Range<usize>,
82 ) -> Self {
83 Self::new(slice_type, source_slice, templated_slice)
84 }
85
86 pub const fn slice_kind(&self) -> TemplateSliceKind {
87 self.slice_type
88 }
89
90 pub fn has_slice_kind(&self, kind: TemplateSliceKind) -> bool {
91 self.slice_kind() == kind
92 }
93}
94
95#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
101pub struct TemplatedFile {
102 inner: Arc<TemplatedFileInner>,
103}
104
105impl TemplatedFile {
106 pub fn new(
107 source_str: String,
108 name: String,
109 input_templated_str: Option<String>,
110 sliced_file: Option<Vec<TemplatedFileSlice>>,
111 input_raw_sliced: Option<Vec<RawFileSlice>>,
112 ) -> Result<TemplatedFile, SQLFluffSkipFile> {
113 Ok(TemplatedFile {
114 inner: Arc::new(TemplatedFileInner::new(
115 source_str,
116 name,
117 input_templated_str,
118 sliced_file,
119 input_raw_sliced,
120 )?),
121 })
122 }
123
124 pub fn name(&self) -> &str {
125 &self.inner.name
126 }
127
128 pub fn ptr_eq(&self, other: &TemplatedFile) -> bool {
130 Arc::ptr_eq(&self.inner, &other.inner)
131 }
132
133 #[cfg(feature = "stringify")]
134 pub fn to_yaml(&self) -> String {
135 let inner = &*self.inner;
136 serde_yaml::to_string(inner).unwrap()
137 }
138}
139
140impl From<String> for TemplatedFile {
141 fn from(raw: String) -> Self {
142 TemplatedFile {
143 inner: Arc::new(
144 TemplatedFileInner::new(raw, "<string>".to_string(), None, None, None).unwrap(),
145 ),
146 }
147 }
148}
149
150impl From<&str> for TemplatedFile {
151 fn from(raw: &str) -> Self {
152 TemplatedFile {
153 inner: Arc::new(
154 TemplatedFileInner::new(raw.to_string(), "<string>".to_string(), None, None, None)
155 .unwrap(),
156 ),
157 }
158 }
159}
160
161impl Deref for TemplatedFile {
162 type Target = TemplatedFileInner;
163
164 fn deref(&self) -> &Self::Target {
165 &self.inner
166 }
167}
168
169#[cfg_attr(feature = "stringify", derive(Serialize))]
170#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
171pub struct TemplatedFileInner {
172 pub source_str: String,
173 name: String,
174 pub templated_str: Option<String>,
175 source_newlines: Vec<usize>,
176 templated_newlines: Vec<usize>,
177 raw_sliced: Vec<RawFileSlice>,
178 pub sliced_file: Vec<TemplatedFileSlice>,
179}
180
181impl TemplatedFileInner {
182 pub fn new(
187 source_str: String,
188 f_name: String,
189 input_templated_str: Option<String>,
190 sliced_file: Option<Vec<TemplatedFileSlice>>,
191 input_raw_sliced: Option<Vec<RawFileSlice>>,
192 ) -> Result<TemplatedFileInner, SQLFluffSkipFile> {
193 let templated_str = input_templated_str.clone().unwrap_or(source_str.clone());
196
197 let (sliced_file, raw_sliced): (Vec<TemplatedFileSlice>, Vec<RawFileSlice>) =
198 match sliced_file {
199 None => {
200 if templated_str != source_str {
201 panic!("Cannot instantiate a templated file unsliced!")
202 } else if input_raw_sliced.is_some() {
203 panic!("Templated file was not sliced, but not has raw slices.")
204 } else {
205 (
206 vec![TemplatedFileSlice::new_typed(
207 TemplateSliceKind::Literal,
208 0..source_str.len(),
209 0..source_str.len(),
210 )],
211 vec![RawFileSlice::new_typed(
212 source_str.clone(),
213 TemplateSliceKind::Literal,
214 0,
215 None,
216 None,
217 )],
218 )
219 }
220 }
221 Some(sliced_file) => {
222 if let Some(raw_sliced) = input_raw_sliced {
223 (sliced_file, raw_sliced)
224 } else {
225 panic!("Templated file was sliced, but not raw.")
226 }
227 }
228 };
229
230 let source_newlines: Vec<usize> = iter_indices_of_newlines(source_str.as_str()).collect();
232 let templated_newlines: Vec<usize> =
233 iter_indices_of_newlines(templated_str.as_str()).collect();
234
235 let mut pos = 0;
237 for rfs in &raw_sliced {
238 if rfs.source_idx != pos {
239 panic!(
240 "TemplatedFile. Consistency fail on running source length. {} != {}",
241 pos, rfs.source_idx
242 )
243 }
244 pos += rfs.raw.len();
245 }
246 if pos != source_str.len() {
247 panic!(
248 "TemplatedFile. Consistency fail on final source length. {} != {}",
249 pos,
250 source_str.len()
251 )
252 }
253
254 let mut previous_slice: Option<&TemplatedFileSlice> = None;
256 let mut outer_tfs: Option<&TemplatedFileSlice> = None;
257 for tfs in &sliced_file {
258 match &previous_slice {
259 Some(previous_slice) => {
260 if tfs.templated_slice.start != previous_slice.templated_slice.end {
261 return Err(SQLFluffSkipFile::new(
262 "Templated slices found to be non-contiguous.".to_string(),
263 ));
264 }
275 }
276 None => {
277 if tfs.templated_slice.start != 0 {
278 return Err(SQLFluffSkipFile::new(format!(
279 "First templated slice does not start at 0, (found slice {:?})",
280 tfs.templated_slice
281 )));
282 }
283 }
284 }
285 previous_slice = Some(tfs);
286 outer_tfs = Some(tfs)
287 }
288 if !sliced_file.is_empty()
289 && input_templated_str.is_some()
290 && let Some(outer_tfs) = outer_tfs
291 && outer_tfs.templated_slice.end != templated_str.len()
292 {
293 return Err(SQLFluffSkipFile::new(format!(
294 "Last templated slice does not end at end of string, (found slice {:?})",
295 outer_tfs.templated_slice
296 )));
297 }
298
299 Ok(TemplatedFileInner {
300 raw_sliced,
301 source_newlines,
302 templated_newlines,
303 source_str: source_str.clone(),
304 sliced_file,
305 name: f_name,
306 templated_str: Some(templated_str),
307 })
308 }
309
310 pub fn is_templated(&self) -> bool {
312 self.templated_str.is_some()
313 }
314
315 pub fn get_line_pos_of_char_pos(&self, char_pos: usize, source: bool) -> (usize, usize) {
324 let (ref_str, file_str) = if source {
325 (&self.source_newlines, self.source_str.as_str())
326 } else {
327 (
328 &self.templated_newlines,
329 self.templated_str.as_deref().unwrap_or(&self.source_str),
330 )
331 };
332 match ref_str.binary_search(&char_pos) {
333 Ok(nl_idx) | Err(nl_idx) => {
334 let line_start_byte = if nl_idx > 0 {
335 ref_str[nl_idx - 1] + 1
336 } else {
337 0
338 };
339 let line_no = nl_idx + 1;
340 let line_pos = if char_pos <= file_str.len()
345 && file_str.is_char_boundary(line_start_byte)
346 && file_str.is_char_boundary(char_pos)
347 {
348 file_str[line_start_byte..char_pos].chars().count() + 1
349 } else {
350 char_pos.saturating_sub(line_start_byte) + 1
351 };
352 (line_no, line_pos)
353 }
354 }
355 }
356
357 pub fn from_string(raw: SmolStr) -> TemplatedFile {
359 TemplatedFile::new(raw.into(), "<string>".to_string(), None, None, None).unwrap()
361 }
362
363 pub fn templated(&self) -> &str {
365 self.templated_str.as_deref().unwrap()
366 }
367
368 pub fn source_only_slices(&self) -> Vec<RawFileSlice> {
369 let mut ret_buff = vec![];
370 for element in &self.raw_sliced {
371 if element.is_source_only_slice() {
372 ret_buff.push(element.clone());
373 }
374 }
375 ret_buff
376 }
377
378 pub fn raw_sliced(&self) -> &[RawFileSlice] {
380 &self.raw_sliced
381 }
382
383 pub fn find_slice_indices_of_templated_pos(
384 &self,
385 templated_pos: usize,
386 start_idx: Option<usize>,
387 inclusive: Option<bool>,
388 ) -> Option<(usize, usize)> {
389 let start_idx = start_idx.unwrap_or(0);
390 let inclusive = inclusive.unwrap_or(true);
391
392 let mut first_idx: Option<usize> = None;
393 let mut last_idx = start_idx;
394
395 for (idx, elem) in self.sliced_file[start_idx..self.sliced_file.len()]
400 .iter()
401 .enumerate()
402 {
403 last_idx = idx + start_idx;
404 if elem.templated_slice.end >= templated_pos {
405 if first_idx.is_none() {
406 first_idx = Some(idx + start_idx);
407 }
408
409 if elem.templated_slice.start > templated_pos
410 || (!inclusive && elem.templated_slice.end >= templated_pos)
411 {
412 break;
413 }
414 }
415 }
416
417 if last_idx == self.sliced_file.len() - 1 {
419 last_idx += 1;
420 }
421
422 first_idx.map(|first_idx| (first_idx, last_idx))
423 }
424
425 pub fn templated_slice_to_source_slice(
427 &self,
428 template_slice: Range<usize>,
429 ) -> Result<Range<usize>, String> {
430 if self.sliced_file.is_empty() {
431 return Ok(template_slice);
432 }
433
434 let sliced_file = self.sliced_file.clone();
435
436 let (ts_start_sf_start, ts_start_sf_stop) = self
437 .find_slice_indices_of_templated_pos(template_slice.start, None, None)
438 .ok_or("Position not found in templated file")?;
439
440 let ts_start_subsliced_file = &sliced_file[ts_start_sf_start..ts_start_sf_stop];
441
442 let mut insertion_point: isize = -1;
444 for elem in ts_start_subsliced_file.iter() {
445 for &slice_elem in ["start", "stop"].iter() {
447 let elem_val = match slice_elem {
448 "start" => elem.templated_slice.start,
449 "stop" => elem.templated_slice.end,
450 _ => panic!("Unexpected slice_elem"),
451 };
452
453 if elem_val == template_slice.start {
454 let point = if slice_elem == "start" {
455 elem.source_slice.start
456 } else {
457 elem.source_slice.end
458 };
459
460 let point: isize = point.try_into().unwrap();
461 if insertion_point < 0 || point < insertion_point {
462 insertion_point = point;
463 }
464 }
467 }
468 }
469
470 if template_slice.start == template_slice.end {
472 return if insertion_point >= 0 {
474 Ok(zero_slice(insertion_point.try_into().unwrap()))
475 } else if !ts_start_subsliced_file.is_empty()
477 && ts_start_subsliced_file[0].has_slice_kind(TemplateSliceKind::Literal)
478 {
479 let offset =
480 template_slice.start - ts_start_subsliced_file[0].templated_slice.start;
481 Ok(zero_slice(
482 ts_start_subsliced_file[0].source_slice.start + offset,
483 ))
484 } else {
485 Err(format!(
486 "Attempting a single length slice within a templated section! {template_slice:?} within \
487 {ts_start_subsliced_file:?}."
488 ))
489 };
490 }
491
492 let (ts_stop_sf_start, ts_stop_sf_stop) = self
493 .find_slice_indices_of_templated_pos(template_slice.end, None, Some(false))
494 .ok_or("Position not found in templated file")?;
495
496 let mut ts_start_sf_start = ts_start_sf_start;
497 if insertion_point >= 0 {
498 for elem in &sliced_file[ts_start_sf_start..] {
499 let insertion_point: usize = insertion_point.try_into().unwrap();
500 if elem.source_slice.start != insertion_point {
501 ts_start_sf_start += 1;
502 } else {
503 break;
504 }
505 }
506 }
507
508 let subslices = &sliced_file[usize::min(ts_start_sf_start, ts_stop_sf_start)
509 ..usize::max(ts_start_sf_stop, ts_stop_sf_stop)];
510
511 let start_slices = if ts_start_sf_start == ts_start_sf_stop {
512 return match ts_start_sf_start.cmp(&sliced_file.len()) {
513 Ordering::Greater => {
514 panic!("Starting position higher than sliced file position")
515 }
516 Ordering::Less => Ok(sliced_file[1].source_slice.clone()),
517 Ordering::Equal => Ok(sliced_file.last().unwrap().source_slice.clone()),
518 };
519 } else {
520 &sliced_file[ts_start_sf_start..ts_start_sf_stop]
521 };
522
523 let stop_slices = if ts_stop_sf_start == ts_stop_sf_stop {
524 vec![sliced_file[ts_stop_sf_start].clone()]
525 } else {
526 sliced_file[ts_stop_sf_start..ts_stop_sf_stop].to_vec()
527 };
528
529 let source_start: isize = if insertion_point >= 0 {
530 insertion_point
531 } else if start_slices[0].has_slice_kind(TemplateSliceKind::Literal) {
532 let offset = template_slice.start - start_slices[0].templated_slice.start;
533 (start_slices[0].source_slice.start + offset)
534 .try_into()
535 .unwrap()
536 } else {
537 start_slices[0].source_slice.start.try_into().unwrap()
538 };
539
540 let source_stop = if stop_slices
541 .last()
542 .unwrap()
543 .has_slice_kind(TemplateSliceKind::Literal)
544 {
545 let offset = stop_slices.last().unwrap().templated_slice.end - template_slice.end;
546 stop_slices.last().unwrap().source_slice.end - offset
547 } else {
548 stop_slices.last().unwrap().source_slice.end
549 };
550
551 let source_slice;
552 if source_start > source_stop.try_into().unwrap() {
553 let mut source_start = usize::MAX;
554 let mut source_stop = 0;
555 for elem in subslices {
556 source_start = usize::min(source_start, elem.source_slice.start);
557 source_stop = usize::max(source_stop, elem.source_slice.end);
558 }
559 source_slice = source_start..source_stop;
560 } else {
561 source_slice = source_start.try_into().unwrap()..source_stop;
562 }
563
564 Ok(source_slice)
565 }
566
567 pub fn is_source_slice_literal(&self, source_slice: &Range<usize>) -> bool {
569 if self.raw_sliced.is_empty() {
571 return true;
572 };
573
574 if source_slice.start == source_slice.end {
576 return true;
577 };
578
579 let mut is_literal = true;
580 for raw_slice in &self.raw_sliced {
581 if raw_slice.source_idx <= source_slice.start {
584 is_literal = raw_slice.has_slice_kind(TemplateSliceKind::Literal);
585 } else if raw_slice.source_idx >= source_slice.end {
586 break;
587 } else if !raw_slice.has_slice_kind(TemplateSliceKind::Literal) {
588 is_literal = false;
589 };
590 }
591 is_literal
592 }
593
594 pub fn raw_slices_spanning_source_slice(
596 &self,
597 source_slice: &Range<usize>,
598 ) -> Vec<RawFileSlice> {
599 let last_raw_slice = self.raw_sliced.last().unwrap();
601 if source_slice.start >= last_raw_slice.source_idx + last_raw_slice.raw.len() {
602 return Vec::new();
603 }
604
605 let mut raw_slice_idx = 0;
607 while raw_slice_idx + 1 < self.raw_sliced.len()
609 && self.raw_sliced[raw_slice_idx + 1].source_idx <= source_slice.start
610 {
611 raw_slice_idx += 1;
612 }
613
614 let mut slice_span = 1;
616 while raw_slice_idx + slice_span < self.raw_sliced.len()
617 && self.raw_sliced[raw_slice_idx + slice_span].source_idx < source_slice.end
618 {
619 slice_span += 1;
620 }
621
622 self.raw_sliced[raw_slice_idx..(raw_slice_idx + slice_span)].to_vec()
624 }
625}
626
627pub fn iter_indices_of_newlines(raw_str: &str) -> impl Iterator<Item = usize> + '_ {
629 raw_str.match_indices('\n').map(|(idx, _)| idx)
631}
632
633#[cfg_attr(feature = "stringify", derive(Serialize, Deserialize))]
634#[derive(Debug, PartialEq, Eq, Clone, Hash)]
635pub enum RawFileSliceType {
636 Comment,
637 BlockEnd,
638 BlockStart,
639 BlockMid,
640}
641
642#[cfg_attr(feature = "stringify", derive(Serialize, Deserialize))]
644#[derive(Debug, PartialEq, Eq, Clone, Hash)]
645pub struct RawFileSlice {
646 raw: String,
648 pub slice_type: TemplateSliceKind,
649 pub source_idx: usize,
651 slice_subtype: Option<RawFileSliceType>,
652 block_idx: usize,
654}
655
656impl RawFileSlice {
657 pub fn new(
658 raw: String,
659 slice_type: TemplateSliceKind,
660 source_idx: usize,
661 slice_subtype: Option<RawFileSliceType>,
662 block_idx: Option<usize>,
663 ) -> Self {
664 Self {
665 raw,
666 slice_type,
667 source_idx,
668 slice_subtype,
669 block_idx: block_idx.unwrap_or(0),
670 }
671 }
672
673 pub fn new_typed(
674 raw: String,
675 slice_type: TemplateSliceKind,
676 source_idx: usize,
677 slice_subtype: Option<RawFileSliceType>,
678 block_idx: Option<usize>,
679 ) -> Self {
680 Self::new(raw, slice_type, source_idx, slice_subtype, block_idx)
681 }
682}
683
684impl RawFileSlice {
685 fn end_source_idx(&self) -> usize {
687 self.source_idx + self.raw.len()
688 }
689
690 pub fn source_slice(&self) -> Range<usize> {
692 self.source_idx..self.end_source_idx()
693 }
694
695 pub fn raw(&self) -> &str {
697 &self.raw
698 }
699
700 pub const fn block_idx(&self) -> usize {
701 self.block_idx
702 }
703
704 pub const fn slice_type(&self) -> TemplateSliceKind {
706 self.slice_type
707 }
708
709 pub const fn slice_kind(&self) -> TemplateSliceKind {
710 self.slice_type
711 }
712
713 pub fn has_slice_kind(&self, kind: TemplateSliceKind) -> bool {
714 self.slice_kind() == kind
715 }
716
717 fn is_source_only_slice(&self) -> bool {
722 self.slice_kind().is_source_only()
723 }
724}
725
726pub fn char_to_byte_indices(s: &str) -> Vec<usize> {
736 let mut indices: Vec<usize> = s.char_indices().map(|(byte_idx, _)| byte_idx).collect();
737 indices.push(s.len());
738 indices
739}
740
741pub fn char_idx_to_byte_idx(char_to_byte: &[usize], char_idx: usize) -> usize {
750 assert!(
751 char_idx < char_to_byte.len(),
752 "char_idx_to_byte_idx: char_idx {char_idx} out of bounds for mapping of length {}",
753 char_to_byte.len()
754 );
755 char_to_byte[char_idx]
756}
757
758#[cfg(test)]
759mod tests {
760 use super::*;
761
762 #[test]
763 fn test_char_to_byte_indices_ascii() {
764 let indices = char_to_byte_indices("hello");
765 assert_eq!(indices, vec![0, 1, 2, 3, 4, 5]);
766 }
767
768 #[test]
769 fn test_char_to_byte_indices_multibyte() {
770 let indices = char_to_byte_indices("あいう");
772 assert_eq!(indices, vec![0, 3, 6, 9]);
773 }
774
775 #[test]
776 fn test_char_to_byte_indices_mixed() {
777 let indices = char_to_byte_indices("aあb");
779 assert_eq!(indices, vec![0, 1, 4, 5]);
780 }
781
782 #[test]
783 fn test_char_to_byte_indices_accented() {
784 let indices = char_to_byte_indices("café");
786 assert_eq!(indices, vec![0, 1, 2, 3, 5]);
787 }
788
789 #[test]
790 fn test_char_to_byte_indices_empty() {
791 let indices = char_to_byte_indices("");
792 assert_eq!(indices, vec![0]);
793 }
794
795 #[test]
796 fn test_char_idx_to_byte_idx_conversion() {
797 let indices = char_to_byte_indices("aあb");
798 assert_eq!(char_idx_to_byte_idx(&indices, 0), 0);
799 assert_eq!(char_idx_to_byte_idx(&indices, 1), 1);
800 assert_eq!(char_idx_to_byte_idx(&indices, 2), 4);
801 assert_eq!(char_idx_to_byte_idx(&indices, 3), 5);
802 }
803
804 #[test]
805 fn test_templated_file_multibyte_consistency_check() {
806 let source = "-- 日本語\nSELECT 1".to_string();
817 assert_eq!(source.len(), 21);
818
819 let raw_sliced = vec![RawFileSlice::new(
820 source.clone(),
821 TemplateSliceKind::Literal,
822 0,
823 None,
824 None,
825 )];
826 let sliced_file = vec![TemplatedFileSlice::new(
827 TemplateSliceKind::Literal,
828 0..source.len(),
829 0..source.len(),
830 )];
831
832 let tf = TemplatedFile::new(
834 source.clone(),
835 "test.sql".to_string(),
836 Some(source.clone()),
837 Some(sliced_file),
838 Some(raw_sliced),
839 )
840 .unwrap();
841 assert_eq!(tf.source_str, source);
842 }
843
844 #[test]
845 fn test_templated_file_multibyte_multiple_raw_slices() {
846 let source = "SELECT 'café'".to_string();
852 assert_eq!(source.len(), 14);
853
854 let raw_sliced = vec![
855 RawFileSlice::new(
856 "SELECT '".to_string(),
857 TemplateSliceKind::Literal,
858 0,
859 None,
860 None,
861 ),
862 RawFileSlice::new(
863 "café".to_string(),
864 TemplateSliceKind::Templated,
865 8, None,
867 None,
868 ),
869 RawFileSlice::new(
870 "'".to_string(),
871 TemplateSliceKind::Literal,
872 13, None,
874 None,
875 ),
876 ];
877 let sliced_file = vec![
878 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..8, 0..8),
879 TemplatedFileSlice::new(TemplateSliceKind::Templated, 8..13, 8..13),
880 TemplatedFileSlice::new(TemplateSliceKind::Literal, 13..14, 13..14),
881 ];
882
883 let tf = TemplatedFile::new(
884 source.clone(),
885 "test.sql".to_string(),
886 Some(source.clone()),
887 Some(sliced_file),
888 Some(raw_sliced),
889 )
890 .unwrap();
891 assert_eq!(tf.source_str, source);
892 }
893
894 #[test]
895 #[should_panic(expected = "Consistency fail on running source length")]
896 fn test_templated_file_char_indices_cause_panic() {
897 let source = "aあb".to_string();
904
905 let raw_sliced = vec![
906 RawFileSlice::new(
907 "aあ".to_string(), TemplateSliceKind::Literal,
909 0,
910 None,
911 None,
912 ),
913 RawFileSlice::new(
914 "b".to_string(),
915 TemplateSliceKind::Literal,
916 2, None,
918 None,
919 ),
920 ];
921 let sliced_file = vec![
922 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..2, 0..2),
923 TemplatedFileSlice::new(TemplateSliceKind::Literal, 2..3, 2..3),
924 ];
925
926 let _ = TemplatedFile::new(
928 source,
929 "test.sql".to_string(),
930 Some("aあb".to_string()),
931 Some(sliced_file),
932 Some(raw_sliced),
933 );
934 }
935
936 #[test]
937 fn test_indices_of_newlines() {
938 vec![
939 ("", vec![]),
940 ("foo", vec![]),
941 ("foo\nbar", vec![3]),
942 ("\nfoo\n\nbar\nfoo\n\nbar\n", vec![0, 4, 5, 9, 13, 14, 18]),
943 ]
944 .into_iter()
945 .for_each(|(in_str, expected)| {
946 assert_eq!(
947 expected,
948 iter_indices_of_newlines(in_str).collect::<Vec<usize>>()
949 )
950 });
951 }
952
953 fn simple_sliced_file() -> Vec<TemplatedFileSlice> {
957 vec![
958 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..10, 0..10),
959 TemplatedFileSlice::new(TemplateSliceKind::Templated, 10..17, 10..12),
960 TemplatedFileSlice::new(TemplateSliceKind::Literal, 17..25, 12..20),
961 ]
962 }
963
964 fn simple_raw_sliced_file() -> [RawFileSlice; 3] {
965 [
966 RawFileSlice::new("x".repeat(10), TemplateSliceKind::Literal, 0, None, None),
967 RawFileSlice::new("x".repeat(7), TemplateSliceKind::Templated, 10, None, None),
968 RawFileSlice::new("x".repeat(8), TemplateSliceKind::Literal, 17, None, None),
969 ]
970 }
971
972 fn complex_sliced_file() -> Vec<TemplatedFileSlice> {
973 vec![
974 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..13, 0..13),
975 TemplatedFileSlice::new(TemplateSliceKind::Comment, 13..29, 13..13),
976 TemplatedFileSlice::new(TemplateSliceKind::Literal, 29..44, 13..28),
977 TemplatedFileSlice::new(TemplateSliceKind::BlockStart, 44..68, 28..28),
978 TemplatedFileSlice::new(TemplateSliceKind::Literal, 68..81, 28..41),
979 TemplatedFileSlice::new(TemplateSliceKind::Templated, 81..86, 41..42),
980 TemplatedFileSlice::new(TemplateSliceKind::Literal, 86..110, 42..66),
981 TemplatedFileSlice::new(TemplateSliceKind::Templated, 68..86, 66..76),
982 TemplatedFileSlice::new(TemplateSliceKind::Literal, 68..81, 76..89),
983 TemplatedFileSlice::new(TemplateSliceKind::Templated, 81..86, 89..90),
984 TemplatedFileSlice::new(TemplateSliceKind::Literal, 86..110, 90..114),
985 TemplatedFileSlice::new(TemplateSliceKind::Templated, 68..86, 114..125),
986 TemplatedFileSlice::new(TemplateSliceKind::Literal, 68..81, 125..138),
987 TemplatedFileSlice::new(TemplateSliceKind::Templated, 81..86, 138..139),
988 TemplatedFileSlice::new(TemplateSliceKind::Literal, 86..110, 139..163),
989 TemplatedFileSlice::new(TemplateSliceKind::Templated, 110..123, 163..166),
990 TemplatedFileSlice::new(TemplateSliceKind::Literal, 123..132, 166..175),
991 TemplatedFileSlice::new(TemplateSliceKind::BlockEnd, 132..144, 175..175),
992 TemplatedFileSlice::new(TemplateSliceKind::Literal, 144..155, 175..186),
993 TemplatedFileSlice::new(TemplateSliceKind::BlockStart, 155..179, 186..186),
994 TemplatedFileSlice::new(TemplateSliceKind::Literal, 179..189, 186..196),
995 TemplatedFileSlice::new(TemplateSliceKind::Templated, 189..194, 196..197),
996 TemplatedFileSlice::new(TemplateSliceKind::Literal, 194..203, 197..206),
997 TemplatedFileSlice::new(TemplateSliceKind::Literal, 179..189, 206..216),
998 TemplatedFileSlice::new(TemplateSliceKind::Templated, 189..194, 216..217),
999 TemplatedFileSlice::new(TemplateSliceKind::Literal, 194..203, 217..226),
1000 TemplatedFileSlice::new(TemplateSliceKind::Literal, 179..189, 226..236),
1001 TemplatedFileSlice::new(TemplateSliceKind::Templated, 189..194, 236..237),
1002 TemplatedFileSlice::new(TemplateSliceKind::Literal, 194..203, 237..246),
1003 TemplatedFileSlice::new(TemplateSliceKind::BlockEnd, 203..215, 246..246),
1004 TemplatedFileSlice::new(TemplateSliceKind::Literal, 215..230, 246..261),
1005 ]
1006 }
1007
1008 fn complex_raw_sliced_file() -> Vec<RawFileSlice> {
1009 vec![
1010 RawFileSlice::new(
1011 "x".repeat(13).to_string(),
1012 TemplateSliceKind::Literal,
1013 0,
1014 None,
1015 None,
1016 ),
1017 RawFileSlice::new(
1018 "x".repeat(16).to_string(),
1019 TemplateSliceKind::Comment,
1020 13,
1021 None,
1022 None,
1023 ),
1024 RawFileSlice::new(
1025 "x".repeat(15).to_string(),
1026 TemplateSliceKind::Literal,
1027 29,
1028 None,
1029 None,
1030 ),
1031 RawFileSlice::new(
1032 "x".repeat(24).to_string(),
1033 TemplateSliceKind::BlockStart,
1034 44,
1035 None,
1036 None,
1037 ),
1038 RawFileSlice::new(
1039 "x".repeat(13).to_string(),
1040 TemplateSliceKind::Literal,
1041 68,
1042 None,
1043 None,
1044 ),
1045 RawFileSlice::new(
1046 "x".repeat(5).to_string(),
1047 TemplateSliceKind::Templated,
1048 81,
1049 None,
1050 None,
1051 ),
1052 RawFileSlice::new(
1053 "x".repeat(24).to_string(),
1054 TemplateSliceKind::Literal,
1055 86,
1056 None,
1057 None,
1058 ),
1059 RawFileSlice::new(
1060 "x".repeat(13).to_string(),
1061 TemplateSliceKind::Templated,
1062 110,
1063 None,
1064 None,
1065 ),
1066 RawFileSlice::new(
1067 "x".repeat(9).to_string(),
1068 TemplateSliceKind::Literal,
1069 123,
1070 None,
1071 None,
1072 ),
1073 RawFileSlice::new(
1074 "x".repeat(12).to_string(),
1075 TemplateSliceKind::BlockEnd,
1076 132,
1077 None,
1078 None,
1079 ),
1080 RawFileSlice::new(
1081 "x".repeat(11).to_string(),
1082 TemplateSliceKind::Literal,
1083 144,
1084 None,
1085 None,
1086 ),
1087 RawFileSlice::new(
1088 "x".repeat(24).to_string(),
1089 TemplateSliceKind::BlockStart,
1090 155,
1091 None,
1092 None,
1093 ),
1094 RawFileSlice::new(
1095 "x".repeat(10).to_string(),
1096 TemplateSliceKind::Literal,
1097 179,
1098 None,
1099 None,
1100 ),
1101 RawFileSlice::new(
1102 "x".repeat(5).to_string(),
1103 TemplateSliceKind::Templated,
1104 189,
1105 None,
1106 None,
1107 ),
1108 RawFileSlice::new(
1109 "x".repeat(9).to_string(),
1110 TemplateSliceKind::Literal,
1111 194,
1112 None,
1113 None,
1114 ),
1115 RawFileSlice::new(
1116 "x".repeat(12).to_string(),
1117 TemplateSliceKind::BlockEnd,
1118 203,
1119 None,
1120 None,
1121 ),
1122 RawFileSlice::new(
1123 "x".repeat(15).to_string(),
1124 TemplateSliceKind::Literal,
1125 215,
1126 None,
1127 None,
1128 ),
1129 ]
1130 }
1131
1132 struct FileKwargs {
1133 f_name: String,
1134 source_str: String,
1135 templated_str: Option<String>,
1136 sliced_file: Vec<TemplatedFileSlice>,
1137 raw_sliced_file: Vec<RawFileSlice>,
1138 }
1139
1140 fn simple_file_kwargs() -> FileKwargs {
1141 FileKwargs {
1142 f_name: "test.sql".to_string(),
1143 source_str: "01234\n6789{{foo}}fo\nbarss".to_string(),
1144 templated_str: Some("01234\n6789x\nfo\nbarss".to_string()),
1145 sliced_file: simple_sliced_file().to_vec(),
1146 raw_sliced_file: simple_raw_sliced_file().to_vec(),
1147 }
1148 }
1149
1150 fn complex_file_kwargs() -> FileKwargs {
1151 FileKwargs {
1152 f_name: "test.sql".to_string(),
1153 source_str: complex_raw_sliced_file()
1154 .iter()
1155 .fold(String::new(), |acc, x| acc + &x.raw),
1156 templated_str: None,
1157 sliced_file: complex_sliced_file().to_vec(),
1158 raw_sliced_file: complex_raw_sliced_file().to_vec(),
1159 }
1160 }
1161
1162 #[test]
1163 fn test_templated_file_get_line_pos_of_char_pos() {
1165 let tests = [
1166 (simple_file_kwargs(), 0, 1, 1),
1167 (simple_file_kwargs(), 20, 3, 1),
1168 (simple_file_kwargs(), 24, 3, 5),
1169 ];
1170
1171 for test in tests {
1172 let kwargs = test.0;
1173
1174 let tf = TemplatedFile::new(
1175 kwargs.source_str,
1176 kwargs.f_name,
1177 kwargs.templated_str,
1178 Some(kwargs.sliced_file),
1179 Some(kwargs.raw_sliced_file),
1180 )
1181 .unwrap();
1182
1183 let (res_line_no, res_line_pos) = tf.get_line_pos_of_char_pos(test.1, true);
1184
1185 assert_eq!(res_line_no, test.2);
1186 assert_eq!(res_line_pos, test.3);
1187 }
1188 }
1189
1190 #[test]
1191 fn test_templated_file_find_slice_indices_of_templated_pos() {
1192 let tests = vec![
1193 (12, true, simple_file_kwargs(), 1, 3),
1200 (20, true, simple_file_kwargs(), 2, 3),
1201 ];
1204
1205 for test in tests {
1206 let args = test.2;
1207
1208 let file = TemplatedFile::new(
1209 args.source_str,
1210 args.f_name,
1211 args.templated_str,
1212 Some(args.sliced_file),
1213 Some(args.raw_sliced_file),
1214 )
1215 .unwrap();
1216
1217 let (res_start, res_stop) = file
1218 .find_slice_indices_of_templated_pos(test.0, None, Some(test.1))
1219 .unwrap();
1220
1221 assert_eq!(res_start, test.3);
1222 assert_eq!(res_stop, test.4);
1223 }
1224 }
1225
1226 #[test]
1227 fn test_templated_file_templated_slice_to_source_slice() {
1229 let test_cases = vec![
1230 (
1232 5..10,
1233 5..10,
1234 true,
1235 FileKwargs {
1236 sliced_file: vec![TemplatedFileSlice::new(
1237 TemplateSliceKind::Literal,
1238 0..20,
1239 0..20,
1240 )],
1241 raw_sliced_file: vec![RawFileSlice::new(
1242 "x".repeat(20),
1243 TemplateSliceKind::Literal,
1244 0,
1245 None,
1246 None,
1247 )],
1248 source_str: "x".repeat(20),
1249 f_name: "foo.sql".to_string(),
1250 templated_str: None,
1251 },
1252 ),
1253 (10..13, 10..13, true, complex_file_kwargs()),
1255 (
1257 5..10,
1258 55..60,
1259 true,
1260 FileKwargs {
1261 sliced_file: vec![TemplatedFileSlice::new(
1262 TemplateSliceKind::Literal,
1263 50..70,
1264 0..20,
1265 )],
1266 raw_sliced_file: vec![
1267 RawFileSlice::new(
1268 "x".repeat(50),
1269 TemplateSliceKind::Literal,
1270 0,
1271 None,
1272 None,
1273 ),
1274 RawFileSlice::new(
1275 "x".repeat(20),
1276 TemplateSliceKind::Literal,
1277 50,
1278 None,
1279 None,
1280 ),
1281 ],
1282 source_str: "x".repeat(70),
1283 f_name: "foo.sql".to_string(),
1284 templated_str: None,
1285 },
1286 ),
1287 (5..15, 5..20, false, simple_file_kwargs()),
1289 (
1291 5..15,
1292 0..25,
1293 false,
1294 FileKwargs {
1295 sliced_file: simple_file_kwargs()
1296 .sliced_file
1297 .iter()
1298 .map(|slc| {
1299 TemplatedFileSlice::new(
1300 TemplateSliceKind::Templated,
1301 slc.source_slice.clone(),
1302 slc.templated_slice.clone(),
1303 )
1304 })
1305 .collect(),
1306 raw_sliced_file: simple_file_kwargs()
1307 .raw_sliced_file
1308 .iter()
1309 .map(|slc| {
1310 RawFileSlice::new(
1311 slc.raw.to_string(),
1312 TemplateSliceKind::Templated,
1313 slc.source_idx,
1314 None,
1315 None,
1316 )
1317 })
1318 .collect(),
1319 ..simple_file_kwargs()
1320 },
1321 ),
1322 (10..10, 10..10, true, simple_file_kwargs()),
1324 (12..12, 17..17, true, simple_file_kwargs()),
1325 (
1327 20..20,
1328 25..25,
1329 true,
1330 FileKwargs {
1331 sliced_file: simple_file_kwargs()
1332 .sliced_file
1333 .into_iter()
1334 .chain(vec![TemplatedFileSlice::new(
1335 TemplateSliceKind::Comment,
1336 25..35,
1337 20..20,
1338 )])
1339 .collect(),
1340 raw_sliced_file: simple_file_kwargs()
1341 .raw_sliced_file
1342 .into_iter()
1343 .chain(vec![RawFileSlice::new(
1344 "x".repeat(10),
1345 TemplateSliceKind::Comment,
1346 25,
1347 None,
1348 None,
1349 )])
1350 .collect(),
1351 source_str: simple_file_kwargs().source_str.to_string() + &"x".repeat(10),
1352 ..simple_file_kwargs()
1353 },
1354 ),
1355 (43..43, 87..87, true, complex_file_kwargs()),
1357 (13..13, 13..13, true, complex_file_kwargs()),
1358 (186..186, 155..155, true, complex_file_kwargs()),
1359 (
1361 100..130,
1362 68..110,
1365 false,
1366 complex_file_kwargs(),
1367 ),
1368 ];
1369
1370 for (in_slice, out_slice, is_literal, tf_kwargs) in test_cases {
1371 let file = TemplatedFile::new(
1372 tf_kwargs.source_str,
1373 tf_kwargs.f_name,
1374 tf_kwargs.templated_str,
1375 Some(tf_kwargs.sliced_file),
1376 Some(tf_kwargs.raw_sliced_file),
1377 )
1378 .unwrap();
1379
1380 let source_slice = file.templated_slice_to_source_slice(in_slice).unwrap();
1381 let literal_test = file.is_source_slice_literal(&source_slice);
1382
1383 assert_eq!((is_literal, source_slice), (literal_test, out_slice));
1384 }
1385 }
1386
1387 #[test]
1388 fn test_templated_file_source_only_slices() {
1390 let test_cases = vec![
1391 (
1393 TemplatedFile::new(
1394 format!("{}{}{}", "a".repeat(10), "{# b #}", "a".repeat(10)),
1395 "test".to_string(),
1396 None,
1397 Some(vec![
1398 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..10, 0..10),
1399 TemplatedFileSlice::new(TemplateSliceKind::Templated, 10..17, 10..10),
1400 TemplatedFileSlice::new(TemplateSliceKind::Literal, 17..27, 10..20),
1401 ]),
1402 Some(vec![
1403 RawFileSlice::new(
1404 "a".repeat(10).to_string(),
1405 TemplateSliceKind::Literal,
1406 0,
1407 None,
1408 None,
1409 ),
1410 RawFileSlice::new(
1411 "{# b #}".to_string(),
1412 TemplateSliceKind::Comment,
1413 10,
1414 None,
1415 None,
1416 ),
1417 RawFileSlice::new(
1418 "a".repeat(10).to_string(),
1419 TemplateSliceKind::Literal,
1420 17,
1421 None,
1422 None,
1423 ),
1424 ]),
1425 )
1426 .unwrap(),
1427 vec![RawFileSlice::new(
1428 "{# b #}".to_string(),
1429 TemplateSliceKind::Comment,
1430 10,
1431 None,
1432 None,
1433 )],
1434 ),
1435 (
1437 TemplatedFile::new(
1438 "aaa{{ b }}aaa".to_string(),
1439 "test".to_string(),
1440 None,
1441 Some(vec![
1442 TemplatedFileSlice::new(TemplateSliceKind::Literal, 0..3, 0..3),
1443 TemplatedFileSlice::new(TemplateSliceKind::Templated, 3..10, 3..6),
1444 TemplatedFileSlice::new(TemplateSliceKind::Literal, 10..13, 6..9),
1445 ]),
1446 Some(vec![
1447 RawFileSlice::new(
1448 "aaa".to_string(),
1449 TemplateSliceKind::Literal,
1450 0,
1451 None,
1452 None,
1453 ),
1454 RawFileSlice::new(
1455 "{{ b }}".to_string(),
1456 TemplateSliceKind::Templated,
1457 3,
1458 None,
1459 None,
1460 ),
1461 RawFileSlice::new(
1462 "aaa".to_string(),
1463 TemplateSliceKind::Literal,
1464 10,
1465 None,
1466 None,
1467 ),
1468 ]),
1469 )
1470 .unwrap(),
1471 vec![],
1472 ),
1473 ];
1474
1475 for (file, expected) in test_cases {
1476 assert_eq!(file.source_only_slices(), expected, "Failed for {:?}", file);
1477 }
1478 }
1479
1480 #[test]
1481 fn template_slice_kind_parses_legacy_strings() {
1482 assert_eq!(
1483 TemplateSliceKind::from_slice_type("block_start").unwrap(),
1484 TemplateSliceKind::BlockStart
1485 );
1486 }
1487
1488 #[test]
1489 fn raw_file_slice_source_only_uses_typed_adapter() {
1490 let comment = RawFileSlice::new_typed(
1491 "/* comment */".to_string(),
1492 TemplateSliceKind::Comment,
1493 0,
1494 None,
1495 None,
1496 );
1497 let literal = RawFileSlice::new_typed(
1498 "select".to_string(),
1499 TemplateSliceKind::Literal,
1500 0,
1501 None,
1502 None,
1503 );
1504
1505 assert!(comment.is_source_only_slice());
1506 assert!(!literal.is_source_only_slice());
1507 }
1508}