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))]
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct TemplatedFileSlice {
17 pub slice_type: String,
18 pub source_slice: Range<usize>,
19 pub templated_slice: Range<usize>,
20}
21
22impl TemplatedFileSlice {
23 pub fn new(
24 slice_type: &str,
25 source_slice: Range<usize>,
26 templated_slice: Range<usize>,
27 ) -> Self {
28 Self {
29 slice_type: slice_type.to_string(),
30 source_slice,
31 templated_slice,
32 }
33 }
34}
35
36#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
42pub struct TemplatedFile {
43 inner: Arc<TemplatedFileInner>,
44}
45
46impl TemplatedFile {
47 pub fn new(
48 source_str: String,
49 name: String,
50 input_templated_str: Option<String>,
51 sliced_file: Option<Vec<TemplatedFileSlice>>,
52 input_raw_sliced: Option<Vec<RawFileSlice>>,
53 ) -> Result<TemplatedFile, SQLFluffSkipFile> {
54 Ok(TemplatedFile {
55 inner: Arc::new(TemplatedFileInner::new(
56 source_str,
57 name,
58 input_templated_str,
59 sliced_file,
60 input_raw_sliced,
61 )?),
62 })
63 }
64
65 pub fn name(&self) -> &str {
66 &self.inner.name
67 }
68
69 #[cfg(feature = "stringify")]
70 pub fn to_yaml(&self) -> String {
71 let inner = &*self.inner;
72 serde_yaml::to_string(inner).unwrap()
73 }
74}
75
76impl From<String> for TemplatedFile {
77 fn from(raw: String) -> Self {
78 TemplatedFile {
79 inner: Arc::new(
80 TemplatedFileInner::new(raw, "<string>".to_string(), None, None, None).unwrap(),
81 ),
82 }
83 }
84}
85
86impl From<&str> for TemplatedFile {
87 fn from(raw: &str) -> Self {
88 TemplatedFile {
89 inner: Arc::new(
90 TemplatedFileInner::new(raw.to_string(), "<string>".to_string(), None, None, None)
91 .unwrap(),
92 ),
93 }
94 }
95}
96
97impl Deref for TemplatedFile {
98 type Target = TemplatedFileInner;
99
100 fn deref(&self) -> &Self::Target {
101 &self.inner
102 }
103}
104
105#[cfg_attr(feature = "stringify", derive(Serialize))]
106#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
107pub struct TemplatedFileInner {
108 pub source_str: String,
109 name: String,
110 pub templated_str: Option<String>,
111 source_newlines: Vec<usize>,
112 templated_newlines: Vec<usize>,
113 raw_sliced: Vec<RawFileSlice>,
114 pub sliced_file: Vec<TemplatedFileSlice>,
115}
116
117impl TemplatedFileInner {
118 pub fn new(
123 source_str: String,
124 f_name: String,
125 input_templated_str: Option<String>,
126 sliced_file: Option<Vec<TemplatedFileSlice>>,
127 input_raw_sliced: Option<Vec<RawFileSlice>>,
128 ) -> Result<TemplatedFileInner, SQLFluffSkipFile> {
129 let templated_str = input_templated_str.clone().unwrap_or(source_str.clone());
132
133 let (sliced_file, raw_sliced): (Vec<TemplatedFileSlice>, Vec<RawFileSlice>) =
134 match sliced_file {
135 None => {
136 if templated_str != source_str {
137 panic!("Cannot instantiate a templated file unsliced!")
138 } else if input_raw_sliced.is_some() {
139 panic!("Templated file was not sliced, but not has raw slices.")
140 } else {
141 (
142 vec![TemplatedFileSlice::new(
143 "literal",
144 0..source_str.len(),
145 0..source_str.len(),
146 )],
147 vec![RawFileSlice::new(
148 source_str.clone(),
149 "literal".to_string(),
150 0,
151 None,
152 None,
153 )],
154 )
155 }
156 }
157 Some(sliced_file) => {
158 if let Some(raw_sliced) = input_raw_sliced {
159 (sliced_file, raw_sliced)
160 } else {
161 panic!("Templated file was sliced, but not raw.")
162 }
163 }
164 };
165
166 let source_newlines: Vec<usize> = iter_indices_of_newlines(source_str.as_str()).collect();
168 let templated_newlines: Vec<usize> =
169 iter_indices_of_newlines(templated_str.as_str()).collect();
170
171 let mut pos = 0;
173 for rfs in &raw_sliced {
174 if rfs.source_idx != pos {
175 panic!(
176 "TemplatedFile. Consistency fail on running source length. {} != {}",
177 pos, rfs.source_idx
178 )
179 }
180 pos += rfs.raw.len();
181 }
182 if pos != source_str.len() {
183 panic!(
184 "TemplatedFile. Consistency fail on final source length. {} != {}",
185 pos,
186 source_str.len()
187 )
188 }
189
190 let mut previous_slice: Option<&TemplatedFileSlice> = None;
192 let mut outer_tfs: Option<&TemplatedFileSlice> = None;
193 for tfs in &sliced_file {
194 match &previous_slice {
195 Some(previous_slice) => {
196 if tfs.templated_slice.start != previous_slice.templated_slice.end {
197 return Err(SQLFluffSkipFile::new(
198 "Templated slices found to be non-contiguous.".to_string(),
199 ));
200 }
211 }
212 None => {
213 if tfs.templated_slice.start != 0 {
214 return Err(SQLFluffSkipFile::new(format!(
215 "First templated slice does not start at 0, (found slice {:?})",
216 tfs.templated_slice
217 )));
218 }
219 }
220 }
221 previous_slice = Some(tfs);
222 outer_tfs = Some(tfs)
223 }
224 if !sliced_file.is_empty()
225 && input_templated_str.is_some()
226 && let Some(outer_tfs) = outer_tfs
227 && outer_tfs.templated_slice.end != templated_str.len()
228 {
229 return Err(SQLFluffSkipFile::new(format!(
230 "Last templated slice does not end at end of string, (found slice {:?})",
231 outer_tfs.templated_slice
232 )));
233 }
234
235 Ok(TemplatedFileInner {
236 raw_sliced,
237 source_newlines,
238 templated_newlines,
239 source_str: source_str.clone(),
240 sliced_file,
241 name: f_name,
242 templated_str: Some(templated_str),
243 })
244 }
245
246 pub fn is_templated(&self) -> bool {
248 self.templated_str.is_some()
249 }
250
251 pub fn get_line_pos_of_char_pos(&self, char_pos: usize, source: bool) -> (usize, usize) {
259 let ref_str = if source {
260 &self.source_newlines
261 } else {
262 &self.templated_newlines
263 };
264 match ref_str.binary_search(&char_pos) {
265 Ok(nl_idx) | Err(nl_idx) => {
266 if nl_idx > 0 {
267 (nl_idx + 1, char_pos - ref_str[nl_idx - 1])
268 } else {
269 (1, char_pos + 1)
272 }
273 }
274 }
275 }
276
277 pub fn from_string(raw: SmolStr) -> TemplatedFile {
279 TemplatedFile::new(raw.into(), "<string>".to_string(), None, None, None).unwrap()
281 }
282
283 pub fn templated(&self) -> &str {
285 self.templated_str.as_deref().unwrap()
286 }
287
288 pub fn source_only_slices(&self) -> Vec<RawFileSlice> {
289 let mut ret_buff = vec![];
290 for element in &self.raw_sliced {
291 if element.is_source_only_slice() {
292 ret_buff.push(element.clone());
293 }
294 }
295 ret_buff
296 }
297
298 pub fn raw_sliced(&self) -> &[RawFileSlice] {
300 &self.raw_sliced
301 }
302
303 pub fn find_slice_indices_of_templated_pos(
304 &self,
305 templated_pos: usize,
306 start_idx: Option<usize>,
307 inclusive: Option<bool>,
308 ) -> Option<(usize, usize)> {
309 let start_idx = start_idx.unwrap_or(0);
310 let inclusive = inclusive.unwrap_or(true);
311
312 let mut first_idx: Option<usize> = None;
313 let mut last_idx = start_idx;
314
315 for (idx, elem) in self.sliced_file[start_idx..self.sliced_file.len()]
320 .iter()
321 .enumerate()
322 {
323 last_idx = idx + start_idx;
324 if elem.templated_slice.end >= templated_pos {
325 if first_idx.is_none() {
326 first_idx = Some(idx + start_idx);
327 }
328
329 if elem.templated_slice.start > templated_pos
330 || (!inclusive && elem.templated_slice.end >= templated_pos)
331 {
332 break;
333 }
334 }
335 }
336
337 if last_idx == self.sliced_file.len() - 1 {
339 last_idx += 1;
340 }
341
342 first_idx.map(|first_idx| (first_idx, last_idx))
343 }
344
345 pub fn templated_slice_to_source_slice(
347 &self,
348 template_slice: Range<usize>,
349 ) -> Result<Range<usize>, String> {
350 if self.sliced_file.is_empty() {
351 return Ok(template_slice);
352 }
353
354 let sliced_file = self.sliced_file.clone();
355
356 let (ts_start_sf_start, ts_start_sf_stop) = self
357 .find_slice_indices_of_templated_pos(template_slice.start, None, None)
358 .ok_or("Position not found in templated file")?;
359
360 let ts_start_subsliced_file = &sliced_file[ts_start_sf_start..ts_start_sf_stop];
361
362 let mut insertion_point: isize = -1;
364 for elem in ts_start_subsliced_file.iter() {
365 for &slice_elem in ["start", "stop"].iter() {
367 let elem_val = match slice_elem {
368 "start" => elem.templated_slice.start,
369 "stop" => elem.templated_slice.end,
370 _ => panic!("Unexpected slice_elem"),
371 };
372
373 if elem_val == template_slice.start {
374 let point = if slice_elem == "start" {
375 elem.source_slice.start
376 } else {
377 elem.source_slice.end
378 };
379
380 let point: isize = point.try_into().unwrap();
381 if insertion_point < 0 || point < insertion_point {
382 insertion_point = point;
383 }
384 }
387 }
388 }
389
390 if template_slice.start == template_slice.end {
392 return if insertion_point >= 0 {
394 Ok(zero_slice(insertion_point.try_into().unwrap()))
395 } else if !ts_start_subsliced_file.is_empty()
397 && ts_start_subsliced_file[0].slice_type == "literal"
398 {
399 let offset =
400 template_slice.start - ts_start_subsliced_file[0].templated_slice.start;
401 Ok(zero_slice(
402 ts_start_subsliced_file[0].source_slice.start + offset,
403 ))
404 } else {
405 Err(format!(
406 "Attempting a single length slice within a templated section! {template_slice:?} within \
407 {ts_start_subsliced_file:?}."
408 ))
409 };
410 }
411
412 let (ts_stop_sf_start, ts_stop_sf_stop) = self
413 .find_slice_indices_of_templated_pos(template_slice.end, None, Some(false))
414 .ok_or("Position not found in templated file")?;
415
416 let mut ts_start_sf_start = ts_start_sf_start;
417 if insertion_point >= 0 {
418 for elem in &sliced_file[ts_start_sf_start..] {
419 let insertion_point: usize = insertion_point.try_into().unwrap();
420 if elem.source_slice.start != insertion_point {
421 ts_start_sf_start += 1;
422 } else {
423 break;
424 }
425 }
426 }
427
428 let subslices = &sliced_file[usize::min(ts_start_sf_start, ts_stop_sf_start)
429 ..usize::max(ts_start_sf_stop, ts_stop_sf_stop)];
430
431 let start_slices = if ts_start_sf_start == ts_start_sf_stop {
432 return match ts_start_sf_start.cmp(&sliced_file.len()) {
433 Ordering::Greater => {
434 panic!("Starting position higher than sliced file position")
435 }
436 Ordering::Less => Ok(sliced_file[1].source_slice.clone()),
437 Ordering::Equal => Ok(sliced_file.last().unwrap().source_slice.clone()),
438 };
439 } else {
440 &sliced_file[ts_start_sf_start..ts_start_sf_stop]
441 };
442
443 let stop_slices = if ts_stop_sf_start == ts_stop_sf_stop {
444 vec![sliced_file[ts_stop_sf_start].clone()]
445 } else {
446 sliced_file[ts_stop_sf_start..ts_stop_sf_stop].to_vec()
447 };
448
449 let source_start: isize = if insertion_point >= 0 {
450 insertion_point
451 } else if start_slices[0].slice_type == "literal" {
452 let offset = template_slice.start - start_slices[0].templated_slice.start;
453 (start_slices[0].source_slice.start + offset)
454 .try_into()
455 .unwrap()
456 } else {
457 start_slices[0].source_slice.start.try_into().unwrap()
458 };
459
460 let source_stop = if stop_slices.last().unwrap().slice_type == "literal" {
461 let offset = stop_slices.last().unwrap().templated_slice.end - template_slice.end;
462 stop_slices.last().unwrap().source_slice.end - offset
463 } else {
464 stop_slices.last().unwrap().source_slice.end
465 };
466
467 let source_slice;
468 if source_start > source_stop.try_into().unwrap() {
469 let mut source_start = usize::MAX;
470 let mut source_stop = 0;
471 for elem in subslices {
472 source_start = usize::min(source_start, elem.source_slice.start);
473 source_stop = usize::max(source_stop, elem.source_slice.end);
474 }
475 source_slice = source_start..source_stop;
476 } else {
477 source_slice = source_start.try_into().unwrap()..source_stop;
478 }
479
480 Ok(source_slice)
481 }
482
483 pub fn is_source_slice_literal(&self, source_slice: &Range<usize>) -> bool {
485 if self.raw_sliced.is_empty() {
487 return true;
488 };
489
490 if source_slice.start == source_slice.end {
492 return true;
493 };
494
495 let mut is_literal = true;
496 for raw_slice in &self.raw_sliced {
497 if raw_slice.source_idx <= source_slice.start {
500 is_literal = raw_slice.slice_type == "literal";
501 } else if raw_slice.source_idx >= source_slice.end {
502 break;
503 } else if raw_slice.slice_type != "literal" {
504 is_literal = false;
505 };
506 }
507 is_literal
508 }
509
510 pub(crate) fn raw_slices_spanning_source_slice(
512 &self,
513 source_slice: &Range<usize>,
514 ) -> Vec<RawFileSlice> {
515 let last_raw_slice = self.raw_sliced.last().unwrap();
517 if source_slice.start >= last_raw_slice.source_idx + last_raw_slice.raw.len() {
518 return Vec::new();
519 }
520
521 let mut raw_slice_idx = 0;
523 while raw_slice_idx + 1 < self.raw_sliced.len()
525 && self.raw_sliced[raw_slice_idx + 1].source_idx <= source_slice.start
526 {
527 raw_slice_idx += 1;
528 }
529
530 let mut slice_span = 1;
532 while raw_slice_idx + slice_span < self.raw_sliced.len()
533 && self.raw_sliced[raw_slice_idx + slice_span].source_idx < source_slice.end
534 {
535 slice_span += 1;
536 }
537
538 self.raw_sliced[raw_slice_idx..(raw_slice_idx + slice_span)].to_vec()
540 }
541}
542
543pub fn iter_indices_of_newlines(raw_str: &str) -> impl Iterator<Item = usize> + '_ {
545 raw_str.match_indices('\n').map(|(idx, _)| idx)
547}
548
549#[cfg_attr(feature = "stringify", derive(Serialize, Deserialize))]
550#[derive(Debug, PartialEq, Eq, Clone, Hash)]
551pub enum RawFileSliceType {
552 Comment,
553 BlockEnd,
554 BlockStart,
555 BlockMid,
556}
557
558#[cfg_attr(feature = "stringify", derive(Serialize, Deserialize))]
560#[derive(Debug, PartialEq, Eq, Clone, Hash)]
561pub struct RawFileSlice {
562 raw: String,
564 pub(crate) slice_type: String,
565 pub source_idx: usize,
567 slice_subtype: Option<RawFileSliceType>,
568 block_idx: usize,
570}
571
572impl RawFileSlice {
573 pub fn new(
574 raw: String,
575 slice_type: String,
576 source_idx: usize,
577 slice_subtype: Option<RawFileSliceType>,
578 block_idx: Option<usize>,
579 ) -> Self {
580 Self {
581 raw,
582 slice_type,
583 source_idx,
584 slice_subtype,
585 block_idx: block_idx.unwrap_or(0),
586 }
587 }
588}
589
590impl RawFileSlice {
591 fn end_source_idx(&self) -> usize {
593 self.source_idx + self.raw.len()
594 }
595
596 pub fn source_slice(&self) -> Range<usize> {
598 self.source_idx..self.end_source_idx()
599 }
600
601 pub fn raw(&self) -> &str {
603 &self.raw
604 }
605
606 pub fn slice_type(&self) -> &str {
608 &self.slice_type
609 }
610
611 fn is_source_only_slice(&self) -> bool {
616 matches!(
619 self.slice_type.as_str(),
620 "comment" | "block_end" | "block_start" | "block_mid"
621 )
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn test_indices_of_newlines() {
631 vec![
632 ("", vec![]),
633 ("foo", vec![]),
634 ("foo\nbar", vec![3]),
635 ("\nfoo\n\nbar\nfoo\n\nbar\n", vec![0, 4, 5, 9, 13, 14, 18]),
636 ]
637 .into_iter()
638 .for_each(|(in_str, expected)| {
639 assert_eq!(
640 expected,
641 iter_indices_of_newlines(in_str).collect::<Vec<usize>>()
642 )
643 });
644 }
645
646 fn simple_sliced_file() -> Vec<TemplatedFileSlice> {
650 vec![
651 TemplatedFileSlice::new("literal", 0..10, 0..10),
652 TemplatedFileSlice::new("templated", 10..17, 10..12),
653 TemplatedFileSlice::new("literal", 17..25, 12..20),
654 ]
655 }
656
657 fn simple_raw_sliced_file() -> [RawFileSlice; 3] {
658 [
659 RawFileSlice::new("x".repeat(10), "literal".to_string(), 0, None, None),
660 RawFileSlice::new("x".repeat(7), "templated".to_string(), 10, None, None),
661 RawFileSlice::new("x".repeat(8), "literal".to_string(), 17, None, None),
662 ]
663 }
664
665 fn complex_sliced_file() -> Vec<TemplatedFileSlice> {
666 vec![
667 TemplatedFileSlice::new("literal", 0..13, 0..13),
668 TemplatedFileSlice::new("comment", 13..29, 13..13),
669 TemplatedFileSlice::new("literal", 29..44, 13..28),
670 TemplatedFileSlice::new("block_start", 44..68, 28..28),
671 TemplatedFileSlice::new("literal", 68..81, 28..41),
672 TemplatedFileSlice::new("templated", 81..86, 41..42),
673 TemplatedFileSlice::new("literal", 86..110, 42..66),
674 TemplatedFileSlice::new("templated", 68..86, 66..76),
675 TemplatedFileSlice::new("literal", 68..81, 76..89),
676 TemplatedFileSlice::new("templated", 81..86, 89..90),
677 TemplatedFileSlice::new("literal", 86..110, 90..114),
678 TemplatedFileSlice::new("templated", 68..86, 114..125),
679 TemplatedFileSlice::new("literal", 68..81, 125..138),
680 TemplatedFileSlice::new("templated", 81..86, 138..139),
681 TemplatedFileSlice::new("literal", 86..110, 139..163),
682 TemplatedFileSlice::new("templated", 110..123, 163..166),
683 TemplatedFileSlice::new("literal", 123..132, 166..175),
684 TemplatedFileSlice::new("block_end", 132..144, 175..175),
685 TemplatedFileSlice::new("literal", 144..155, 175..186),
686 TemplatedFileSlice::new("block_start", 155..179, 186..186),
687 TemplatedFileSlice::new("literal", 179..189, 186..196),
688 TemplatedFileSlice::new("templated", 189..194, 196..197),
689 TemplatedFileSlice::new("literal", 194..203, 197..206),
690 TemplatedFileSlice::new("literal", 179..189, 206..216),
691 TemplatedFileSlice::new("templated", 189..194, 216..217),
692 TemplatedFileSlice::new("literal", 194..203, 217..226),
693 TemplatedFileSlice::new("literal", 179..189, 226..236),
694 TemplatedFileSlice::new("templated", 189..194, 236..237),
695 TemplatedFileSlice::new("literal", 194..203, 237..246),
696 TemplatedFileSlice::new("block_end", 203..215, 246..246),
697 TemplatedFileSlice::new("literal", 215..230, 246..261),
698 ]
699 }
700
701 fn complex_raw_sliced_file() -> Vec<RawFileSlice> {
702 vec![
703 RawFileSlice::new(
704 "x".repeat(13).to_string(),
705 "literal".to_string(),
706 0,
707 None,
708 None,
709 ),
710 RawFileSlice::new(
711 "x".repeat(16).to_string(),
712 "comment".to_string(),
713 13,
714 None,
715 None,
716 ),
717 RawFileSlice::new(
718 "x".repeat(15).to_string(),
719 "literal".to_string(),
720 29,
721 None,
722 None,
723 ),
724 RawFileSlice::new(
725 "x".repeat(24).to_string(),
726 "block_start".to_string(),
727 44,
728 None,
729 None,
730 ),
731 RawFileSlice::new(
732 "x".repeat(13).to_string(),
733 "literal".to_string(),
734 68,
735 None,
736 None,
737 ),
738 RawFileSlice::new(
739 "x".repeat(5).to_string(),
740 "templated".to_string(),
741 81,
742 None,
743 None,
744 ),
745 RawFileSlice::new(
746 "x".repeat(24).to_string(),
747 "literal".to_string(),
748 86,
749 None,
750 None,
751 ),
752 RawFileSlice::new(
753 "x".repeat(13).to_string(),
754 "templated".to_string(),
755 110,
756 None,
757 None,
758 ),
759 RawFileSlice::new(
760 "x".repeat(9).to_string(),
761 "literal".to_string(),
762 123,
763 None,
764 None,
765 ),
766 RawFileSlice::new(
767 "x".repeat(12).to_string(),
768 "block_end".to_string(),
769 132,
770 None,
771 None,
772 ),
773 RawFileSlice::new(
774 "x".repeat(11).to_string(),
775 "literal".to_string(),
776 144,
777 None,
778 None,
779 ),
780 RawFileSlice::new(
781 "x".repeat(24).to_string(),
782 "block_start".to_string(),
783 155,
784 None,
785 None,
786 ),
787 RawFileSlice::new(
788 "x".repeat(10).to_string(),
789 "literal".to_string(),
790 179,
791 None,
792 None,
793 ),
794 RawFileSlice::new(
795 "x".repeat(5).to_string(),
796 "templated".to_string(),
797 189,
798 None,
799 None,
800 ),
801 RawFileSlice::new(
802 "x".repeat(9).to_string(),
803 "literal".to_string(),
804 194,
805 None,
806 None,
807 ),
808 RawFileSlice::new(
809 "x".repeat(12).to_string(),
810 "block_end".to_string(),
811 203,
812 None,
813 None,
814 ),
815 RawFileSlice::new(
816 "x".repeat(15).to_string(),
817 "literal".to_string(),
818 215,
819 None,
820 None,
821 ),
822 ]
823 }
824
825 struct FileKwargs {
826 f_name: String,
827 source_str: String,
828 templated_str: Option<String>,
829 sliced_file: Vec<TemplatedFileSlice>,
830 raw_sliced_file: Vec<RawFileSlice>,
831 }
832
833 fn simple_file_kwargs() -> FileKwargs {
834 FileKwargs {
835 f_name: "test.sql".to_string(),
836 source_str: "01234\n6789{{foo}}fo\nbarss".to_string(),
837 templated_str: Some("01234\n6789x\nfo\nbarss".to_string()),
838 sliced_file: simple_sliced_file().to_vec(),
839 raw_sliced_file: simple_raw_sliced_file().to_vec(),
840 }
841 }
842
843 fn complex_file_kwargs() -> FileKwargs {
844 FileKwargs {
845 f_name: "test.sql".to_string(),
846 source_str: complex_raw_sliced_file()
847 .iter()
848 .fold(String::new(), |acc, x| acc + &x.raw),
849 templated_str: None,
850 sliced_file: complex_sliced_file().to_vec(),
851 raw_sliced_file: complex_raw_sliced_file().to_vec(),
852 }
853 }
854
855 #[test]
856 fn test_templated_file_get_line_pos_of_char_pos() {
858 let tests = [
859 (simple_file_kwargs(), 0, 1, 1),
860 (simple_file_kwargs(), 20, 3, 1),
861 (simple_file_kwargs(), 24, 3, 5),
862 ];
863
864 for test in tests {
865 let kwargs = test.0;
866
867 let tf = TemplatedFile::new(
868 kwargs.source_str,
869 kwargs.f_name,
870 kwargs.templated_str,
871 Some(kwargs.sliced_file),
872 Some(kwargs.raw_sliced_file),
873 )
874 .unwrap();
875
876 let (res_line_no, res_line_pos) = tf.get_line_pos_of_char_pos(test.1, true);
877
878 assert_eq!(res_line_no, test.2);
879 assert_eq!(res_line_pos, test.3);
880 }
881 }
882
883 #[test]
884 fn test_templated_file_find_slice_indices_of_templated_pos() {
885 let tests = vec![
886 (12, true, simple_file_kwargs(), 1, 3),
893 (20, true, simple_file_kwargs(), 2, 3),
894 ];
897
898 for test in tests {
899 let args = test.2;
900
901 let file = TemplatedFile::new(
902 args.source_str,
903 args.f_name,
904 args.templated_str,
905 Some(args.sliced_file),
906 Some(args.raw_sliced_file),
907 )
908 .unwrap();
909
910 let (res_start, res_stop) = file
911 .find_slice_indices_of_templated_pos(test.0, None, Some(test.1))
912 .unwrap();
913
914 assert_eq!(res_start, test.3);
915 assert_eq!(res_stop, test.4);
916 }
917 }
918
919 #[test]
920 fn test_templated_file_templated_slice_to_source_slice() {
922 let test_cases = vec![
923 (
925 5..10,
926 5..10,
927 true,
928 FileKwargs {
929 sliced_file: vec![TemplatedFileSlice::new("literal", 0..20, 0..20)],
930 raw_sliced_file: vec![RawFileSlice::new(
931 "x".repeat(20),
932 "literal".to_string(),
933 0,
934 None,
935 None,
936 )],
937 source_str: "x".repeat(20),
938 f_name: "foo.sql".to_string(),
939 templated_str: None,
940 },
941 ),
942 (10..13, 10..13, true, complex_file_kwargs()),
944 (
946 5..10,
947 55..60,
948 true,
949 FileKwargs {
950 sliced_file: vec![TemplatedFileSlice::new("literal", 50..70, 0..20)],
951 raw_sliced_file: vec![
952 RawFileSlice::new("x".repeat(50), "literal".to_string(), 0, None, None),
953 RawFileSlice::new("x".repeat(20), "literal".to_string(), 50, None, None),
954 ],
955 source_str: "x".repeat(70),
956 f_name: "foo.sql".to_string(),
957 templated_str: None,
958 },
959 ),
960 (5..15, 5..20, false, simple_file_kwargs()),
962 (
964 5..15,
965 0..25,
966 false,
967 FileKwargs {
968 sliced_file: simple_file_kwargs()
969 .sliced_file
970 .iter()
971 .map(|slc| {
972 TemplatedFileSlice::new(
973 "templated",
974 slc.source_slice.clone(),
975 slc.templated_slice.clone(),
976 )
977 })
978 .collect(),
979 raw_sliced_file: simple_file_kwargs()
980 .raw_sliced_file
981 .iter()
982 .map(|slc| {
983 RawFileSlice::new(
984 slc.raw.to_string(),
985 "templated".to_string(),
986 slc.source_idx,
987 None,
988 None,
989 )
990 })
991 .collect(),
992 ..simple_file_kwargs()
993 },
994 ),
995 (10..10, 10..10, true, simple_file_kwargs()),
997 (12..12, 17..17, true, simple_file_kwargs()),
998 (
1000 20..20,
1001 25..25,
1002 true,
1003 FileKwargs {
1004 sliced_file: simple_file_kwargs()
1005 .sliced_file
1006 .into_iter()
1007 .chain(vec![TemplatedFileSlice::new("comment", 25..35, 20..20)])
1008 .collect(),
1009 raw_sliced_file: simple_file_kwargs()
1010 .raw_sliced_file
1011 .into_iter()
1012 .chain(vec![RawFileSlice::new(
1013 "x".repeat(10),
1014 "comment".to_string(),
1015 25,
1016 None,
1017 None,
1018 )])
1019 .collect(),
1020 source_str: simple_file_kwargs().source_str.to_string() + &"x".repeat(10),
1021 ..simple_file_kwargs()
1022 },
1023 ),
1024 (43..43, 87..87, true, complex_file_kwargs()),
1026 (13..13, 13..13, true, complex_file_kwargs()),
1027 (186..186, 155..155, true, complex_file_kwargs()),
1028 (
1030 100..130,
1031 68..110,
1034 false,
1035 complex_file_kwargs(),
1036 ),
1037 ];
1038
1039 for (in_slice, out_slice, is_literal, tf_kwargs) in test_cases {
1040 let file = TemplatedFile::new(
1041 tf_kwargs.source_str,
1042 tf_kwargs.f_name,
1043 tf_kwargs.templated_str,
1044 Some(tf_kwargs.sliced_file),
1045 Some(tf_kwargs.raw_sliced_file),
1046 )
1047 .unwrap();
1048
1049 let source_slice = file.templated_slice_to_source_slice(in_slice).unwrap();
1050 let literal_test = file.is_source_slice_literal(&source_slice);
1051
1052 assert_eq!((is_literal, source_slice), (literal_test, out_slice));
1053 }
1054 }
1055
1056 #[test]
1057 fn test_templated_file_source_only_slices() {
1059 let test_cases = vec![
1060 (
1062 TemplatedFile::new(
1063 format!("{}{}{}", "a".repeat(10), "{# b #}", "a".repeat(10)),
1064 "test".to_string(),
1065 None,
1066 Some(vec![
1067 TemplatedFileSlice::new("literal", 0..10, 0..10),
1068 TemplatedFileSlice::new("templated", 10..17, 10..10),
1069 TemplatedFileSlice::new("literal", 17..27, 10..20),
1070 ]),
1071 Some(vec![
1072 RawFileSlice::new(
1073 "a".repeat(10).to_string(),
1074 "literal".to_string(),
1075 0,
1076 None,
1077 None,
1078 ),
1079 RawFileSlice::new(
1080 "{# b #}".to_string(),
1081 "comment".to_string(),
1082 10,
1083 None,
1084 None,
1085 ),
1086 RawFileSlice::new(
1087 "a".repeat(10).to_string(),
1088 "literal".to_string(),
1089 17,
1090 None,
1091 None,
1092 ),
1093 ]),
1094 )
1095 .unwrap(),
1096 vec![RawFileSlice::new(
1097 "{# b #}".to_string(),
1098 "comment".to_string(),
1099 10,
1100 None,
1101 None,
1102 )],
1103 ),
1104 (
1106 TemplatedFile::new(
1107 "aaa{{ b }}aaa".to_string(),
1108 "test".to_string(),
1109 None,
1110 Some(vec![
1111 TemplatedFileSlice::new("literal", 0..3, 0..3),
1112 TemplatedFileSlice::new("templated", 3..10, 3..6),
1113 TemplatedFileSlice::new("literal", 10..13, 6..9),
1114 ]),
1115 Some(vec![
1116 RawFileSlice::new("aaa".to_string(), "literal".to_string(), 0, None, None),
1117 RawFileSlice::new(
1118 "{{ b }}".to_string(),
1119 "templated".to_string(),
1120 3,
1121 None,
1122 None,
1123 ),
1124 RawFileSlice::new("aaa".to_string(), "literal".to_string(), 10, None, None),
1125 ]),
1126 )
1127 .unwrap(),
1128 vec![],
1129 ),
1130 ];
1131
1132 for (file, expected) in test_cases {
1133 assert_eq!(file.source_only_slices(), expected, "Failed for {:?}", file);
1134 }
1135 }
1136}