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