sqruff_lib_core/templaters/
base.rs

1use std::cmp::Ordering;
2use std::ops::{Deref, Range};
3use std::sync::Arc;
4
5use smol_str::SmolStr;
6
7use crate::errors::{SQLFluffSkipFile, ValueError};
8use crate::slice_helpers::zero_slice;
9
10/// A slice referring to a templated file.
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct TemplatedFileSlice {
13    pub slice_type: String,
14    pub source_slice: Range<usize>,
15    pub templated_slice: Range<usize>,
16}
17
18impl TemplatedFileSlice {
19    pub fn new(
20        slice_type: &str,
21        source_slice: Range<usize>,
22        templated_slice: Range<usize>,
23    ) -> Self {
24        Self {
25            slice_type: slice_type.to_string(),
26            source_slice,
27            templated_slice,
28        }
29    }
30}
31
32/// A templated SQL file.
33///
34/// This is the response of a `templater`'s `.process()` method
35/// and contains both references to the original file and also
36/// the capability to split up that file when lexing.
37#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
38pub struct TemplatedFile {
39    inner: Arc<TemplatedFileInner>,
40}
41
42impl TemplatedFile {
43    pub fn new(
44        source_str: String,
45        f_name: String,
46        input_templated_str: Option<String>,
47        sliced_file: Option<Vec<TemplatedFileSlice>>,
48        input_raw_sliced: Option<Vec<RawFileSlice>>,
49    ) -> Result<TemplatedFile, SQLFluffSkipFile> {
50        Ok(TemplatedFile {
51            inner: Arc::new(TemplatedFileInner::new(
52                source_str,
53                f_name,
54                input_templated_str,
55                sliced_file,
56                input_raw_sliced,
57            )?),
58        })
59    }
60}
61
62impl From<String> for TemplatedFile {
63    fn from(raw: String) -> Self {
64        TemplatedFile {
65            inner: Arc::new(
66                TemplatedFileInner::new(raw, "<string>".to_string(), None, None, None).unwrap(),
67            ),
68        }
69    }
70}
71
72impl From<&str> for TemplatedFile {
73    fn from(raw: &str) -> Self {
74        TemplatedFile {
75            inner: Arc::new(
76                TemplatedFileInner::new(raw.to_string(), "<string>".to_string(), None, None, None)
77                    .unwrap(),
78            ),
79        }
80    }
81}
82
83impl Deref for TemplatedFile {
84    type Target = TemplatedFileInner;
85
86    fn deref(&self) -> &Self::Target {
87        &self.inner
88    }
89}
90
91#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
92pub struct TemplatedFileInner {
93    pub source_str: String,
94    f_name: String,
95    pub templated_str: Option<String>,
96    source_newlines: Vec<usize>,
97    templated_newlines: Vec<usize>,
98    raw_sliced: Vec<RawFileSlice>,
99    pub sliced_file: Vec<TemplatedFileSlice>,
100}
101
102impl TemplatedFileInner {
103    /// Initialise the TemplatedFile.
104    /// If no templated_str is provided then we assume that
105    /// the file is NOT templated and that the templated view
106    /// is the same as the source view.
107    pub fn new(
108        source_str: String,
109        f_name: String,
110        input_templated_str: Option<String>,
111        sliced_file: Option<Vec<TemplatedFileSlice>>,
112        input_raw_sliced: Option<Vec<RawFileSlice>>,
113    ) -> Result<TemplatedFileInner, SQLFluffSkipFile> {
114        // Assume that no sliced_file, means the file is not templated.
115        // TODO Will this not always be Some and so type can avoid Option?
116        let templated_str = input_templated_str.clone().unwrap_or(source_str.clone());
117
118        let (sliced_file, raw_sliced): (Vec<TemplatedFileSlice>, Vec<RawFileSlice>) =
119            match sliced_file {
120                None => {
121                    if templated_str != source_str {
122                        panic!("Cannot instantiate a templated file unsliced!")
123                    } else if input_raw_sliced.is_some() {
124                        panic!("Templated file was not sliced, but not has raw slices.")
125                    } else {
126                        (
127                            vec![TemplatedFileSlice::new(
128                                "literal",
129                                0..source_str.len(),
130                                0..source_str.len(),
131                            )],
132                            vec![RawFileSlice::new(
133                                source_str.clone(),
134                                "literal".to_string(),
135                                0,
136                                None,
137                                None,
138                            )],
139                        )
140                    }
141                }
142                Some(sliced_file) => {
143                    if let Some(raw_sliced) = input_raw_sliced {
144                        (sliced_file, raw_sliced)
145                    } else {
146                        panic!("Templated file was sliced, but not raw.")
147                    }
148                }
149            };
150
151        // Precalculate newlines, character positions.
152        let source_newlines: Vec<usize> = iter_indices_of_newlines(source_str.as_str()).collect();
153        let templated_newlines: Vec<usize> =
154            iter_indices_of_newlines(templated_str.as_str()).collect();
155
156        // Consistency check raw string and slices.
157        let mut pos = 0;
158        for rfs in &raw_sliced {
159            if rfs.source_idx != pos {
160                panic!(
161                    "TemplatedFile. Consistency fail on running source length. {} != {}",
162                    pos, rfs.source_idx
163                )
164            }
165            pos += rfs.raw.len();
166        }
167        if pos != source_str.len() {
168            panic!(
169                "TemplatedFile. Consistency fail on final source length. {} != {}",
170                pos,
171                source_str.len()
172            )
173        }
174
175        // Consistency check templated string and slices.
176        let mut previous_slice: Option<&TemplatedFileSlice> = None;
177        let mut outer_tfs: Option<&TemplatedFileSlice> = None;
178        for tfs in &sliced_file {
179            match &previous_slice {
180                Some(previous_slice) => {
181                    if tfs.templated_slice.start != previous_slice.templated_slice.end {
182                        return Err(SQLFluffSkipFile::new(
183                            "Templated slices found to be non-contiguous.".to_string(),
184                        ));
185                        // TODO Make this nicer again
186                        // format!(
187                        //     "Templated slices found to be non-contiguous.
188                        // {:?} (starting {:?}) does not follow {:?} (starting
189                        // {:?})",
190                        //     tfs.templated_slice,
191                        //     templated_str[tfs.templated_slice],
192                        //     previous_slice.templated_slice,
193                        //     templated_str[previous_slice.templated_slice],
194                        // )
195                    }
196                }
197                None => {
198                    if tfs.templated_slice.start != 0 {
199                        return Err(SQLFluffSkipFile::new(format!(
200                            "First templated slice does not start at 0, (found slice {:?})",
201                            tfs.templated_slice
202                        )));
203                    }
204                }
205            }
206            previous_slice = Some(tfs);
207            outer_tfs = Some(tfs)
208        }
209        if !sliced_file.is_empty() && input_templated_str.is_some() {
210            if let Some(outer_tfs) = outer_tfs {
211                if outer_tfs.templated_slice.end != templated_str.len() {
212                    return Err(SQLFluffSkipFile::new(format!(
213                        "Last templated slice does not end at end of string, (found slice {:?})",
214                        outer_tfs.templated_slice
215                    )));
216                }
217            }
218        }
219
220        Ok(TemplatedFileInner {
221            raw_sliced,
222            source_newlines,
223            templated_newlines,
224            source_str: source_str.clone(),
225            sliced_file,
226            f_name,
227            templated_str: Some(templated_str),
228        })
229    }
230
231    /// Return true if there's a templated file.
232    pub fn is_templated(&self) -> bool {
233        self.templated_str.is_some()
234    }
235
236    /// Get the line number and position of a point in the source file.
237    /// Args:
238    ///  - char_pos: The character position in the relevant file.
239    ///  - source: Are we checking the source file (as opposed to the templated
240    ///    file)
241    ///
242    /// Returns: line_number, line_position
243    pub fn get_line_pos_of_char_pos(&self, char_pos: usize, source: bool) -> (usize, usize) {
244        let ref_str = if source {
245            &self.source_newlines
246        } else {
247            &self.templated_newlines
248        };
249        match ref_str.binary_search(&char_pos) {
250            Ok(nl_idx) | Err(nl_idx) => {
251                if nl_idx > 0 {
252                    (nl_idx + 1, char_pos - ref_str[nl_idx - 1])
253                } else {
254                    // NB: line_pos is char_pos + 1 because character position is 0-indexed,
255                    // but the line position is 1-indexed.
256                    (1, char_pos + 1)
257                }
258            }
259        }
260    }
261
262    /// Create TemplatedFile from a string.
263    pub fn from_string(raw: SmolStr) -> TemplatedFile {
264        // TODO: Might need to deal with this unwrap
265        TemplatedFile::new(raw.into(), "<string>".to_string(), None, None, None).unwrap()
266    }
267
268    /// Get templated string
269    pub fn templated(&self) -> &str {
270        self.templated_str.as_deref().unwrap()
271    }
272
273    pub fn source_only_slices(&self) -> Vec<RawFileSlice> {
274        let mut ret_buff = vec![];
275        for element in &self.raw_sliced {
276            if element.is_source_only_slice() {
277                ret_buff.push(element.clone());
278            }
279        }
280        ret_buff
281    }
282
283    pub fn find_slice_indices_of_templated_pos(
284        &self,
285        templated_pos: usize,
286        start_idx: Option<usize>,
287        inclusive: Option<bool>,
288    ) -> Result<(usize, usize), ValueError> {
289        let start_idx = start_idx.unwrap_or(0);
290        let inclusive = inclusive.unwrap_or(true);
291
292        let mut first_idx: Option<usize> = None;
293        let mut last_idx = start_idx;
294
295        // Work through the sliced file, starting at the start_idx if given
296        // as an optimisation hint. The sliced_file is a list of TemplatedFileSlice
297        // which reference parts of the templated file and where they exist in the
298        // source.
299        for (idx, elem) in self.sliced_file[start_idx..self.sliced_file.len()]
300            .iter()
301            .enumerate()
302        {
303            last_idx = idx + start_idx;
304            if elem.templated_slice.end >= templated_pos {
305                if first_idx.is_none() {
306                    first_idx = Some(idx + start_idx);
307                }
308
309                if elem.templated_slice.start > templated_pos
310                    || (!inclusive && elem.templated_slice.end >= templated_pos)
311                {
312                    break;
313                }
314            }
315        }
316
317        // If we got to the end add another index
318        if last_idx == self.sliced_file.len() - 1 {
319            last_idx += 1;
320        }
321
322        match first_idx {
323            Some(first_idx) => Ok((first_idx, last_idx)),
324            None => Err(ValueError::new("Position Not Found".to_string())),
325        }
326    }
327
328    /// Convert a template slice to a source slice.
329    pub fn templated_slice_to_source_slice(
330        &self,
331        template_slice: Range<usize>,
332    ) -> Result<Range<usize>, ValueError> {
333        if self.sliced_file.is_empty() {
334            return Ok(template_slice);
335        }
336
337        let sliced_file = self.sliced_file.clone();
338
339        let (ts_start_sf_start, ts_start_sf_stop) =
340            self.find_slice_indices_of_templated_pos(template_slice.start, None, None)?;
341
342        let ts_start_subsliced_file = &sliced_file[ts_start_sf_start..ts_start_sf_stop];
343
344        // Work out the insertion point
345        let mut insertion_point: isize = -1;
346        for elem in ts_start_subsliced_file.iter() {
347            // Do slice starts and ends
348            for &slice_elem in ["start", "stop"].iter() {
349                let elem_val = match slice_elem {
350                    "start" => elem.templated_slice.start,
351                    "stop" => elem.templated_slice.end,
352                    _ => panic!("Unexpected slice_elem"),
353                };
354
355                if elem_val == template_slice.start {
356                    let point = if slice_elem == "start" {
357                        elem.source_slice.start
358                    } else {
359                        elem.source_slice.end
360                    };
361
362                    let point: isize = point.try_into().unwrap();
363                    if insertion_point < 0 || point < insertion_point {
364                        insertion_point = point;
365                    }
366                    // We don't break here, because we might find ANOTHER
367                    // later which is actually earlier.
368                }
369            }
370        }
371
372        // Zero length slice.
373        if template_slice.start == template_slice.end {
374            // Is it on a join?
375            return if insertion_point >= 0 {
376                Ok(zero_slice(insertion_point.try_into().unwrap()))
377                // It's within a segment.
378            } else if !ts_start_subsliced_file.is_empty()
379                && ts_start_subsliced_file[0].slice_type == "literal"
380            {
381                let offset =
382                    template_slice.start - ts_start_subsliced_file[0].templated_slice.start;
383                Ok(zero_slice(
384                    ts_start_subsliced_file[0].source_slice.start + offset,
385                ))
386            } else {
387                Err(ValueError::new(format!(
388                    "Attempting a single length slice within a templated section! {:?} within \
389                     {:?}.",
390                    template_slice, ts_start_subsliced_file
391                )))
392            };
393        }
394
395        let (ts_stop_sf_start, ts_stop_sf_stop) =
396            self.find_slice_indices_of_templated_pos(template_slice.end, None, Some(false))?;
397
398        let mut ts_start_sf_start = ts_start_sf_start;
399        if insertion_point >= 0 {
400            for elem in &sliced_file[ts_start_sf_start..] {
401                let insertion_point: usize = insertion_point.try_into().unwrap();
402                if elem.source_slice.start != insertion_point {
403                    ts_start_sf_start += 1;
404                } else {
405                    break;
406                }
407            }
408        }
409
410        let subslices = &sliced_file[usize::min(ts_start_sf_start, ts_stop_sf_start)
411            ..usize::max(ts_start_sf_stop, ts_stop_sf_stop)];
412
413        let start_slices = if ts_start_sf_start == ts_start_sf_stop {
414            return match ts_start_sf_start.cmp(&sliced_file.len()) {
415                Ordering::Greater => Err(ValueError::new(
416                    "Starting position higher than sliced file position".into(),
417                )),
418                Ordering::Less => Ok(sliced_file[1].source_slice.clone()),
419                Ordering::Equal => Ok(sliced_file.last().unwrap().source_slice.clone()),
420            };
421        } else {
422            &sliced_file[ts_start_sf_start..ts_start_sf_stop]
423        };
424
425        let stop_slices = if ts_stop_sf_start == ts_stop_sf_stop {
426            vec![sliced_file[ts_stop_sf_start].clone()]
427        } else {
428            sliced_file[ts_stop_sf_start..ts_stop_sf_stop].to_vec()
429        };
430
431        let source_start: isize = if insertion_point >= 0 {
432            insertion_point
433        } else if start_slices[0].slice_type == "literal" {
434            let offset = template_slice.start - start_slices[0].templated_slice.start;
435            (start_slices[0].source_slice.start + offset)
436                .try_into()
437                .unwrap()
438        } else {
439            start_slices[0].source_slice.start.try_into().unwrap()
440        };
441
442        let source_stop = if stop_slices.last().unwrap().slice_type == "literal" {
443            let offset = stop_slices.last().unwrap().templated_slice.end - template_slice.end;
444            stop_slices.last().unwrap().source_slice.end - offset
445        } else {
446            stop_slices.last().unwrap().source_slice.end
447        };
448
449        let source_slice;
450        if source_start > source_stop.try_into().unwrap() {
451            let mut source_start = usize::MAX;
452            let mut source_stop = 0;
453            for elem in subslices {
454                source_start = usize::min(source_start, elem.source_slice.start);
455                source_stop = usize::max(source_stop, elem.source_slice.end);
456            }
457            source_slice = source_start..source_stop;
458        } else {
459            source_slice = source_start.try_into().unwrap()..source_stop;
460        }
461
462        Ok(source_slice)
463    }
464
465    ///  Work out whether a slice of the source file is a literal or not.
466    pub fn is_source_slice_literal(&self, source_slice: &Range<usize>) -> bool {
467        // No sliced file? Everything is literal
468        if self.raw_sliced.is_empty() {
469            return true;
470        };
471
472        // Zero length slice. It's a literal, because it's definitely not templated.
473        if source_slice.start == source_slice.end {
474            return true;
475        };
476
477        let mut is_literal = true;
478        for raw_slice in &self.raw_sliced {
479            // Reset if we find a literal and we're up to the start
480            // otherwise set false.
481            if raw_slice.source_idx <= source_slice.start {
482                is_literal = raw_slice.slice_type == "literal";
483            } else if raw_slice.source_idx >= source_slice.end {
484                break;
485            } else if raw_slice.slice_type != "literal" {
486                is_literal = false;
487            };
488        }
489        is_literal
490    }
491
492    /// Return a list of the raw slices spanning a set of indices.
493    pub(crate) fn raw_slices_spanning_source_slice(
494        &self,
495        source_slice: &Range<usize>,
496    ) -> Vec<RawFileSlice> {
497        // Special case: The source_slice is at the end of the file.
498        let last_raw_slice = self.raw_sliced.last().unwrap();
499        if source_slice.start >= last_raw_slice.source_idx + last_raw_slice.raw.len() {
500            return Vec::new();
501        }
502
503        // First find the start index
504        let mut raw_slice_idx = 0;
505        // Move the raw pointer forward to the start of this patch
506        while raw_slice_idx + 1 < self.raw_sliced.len()
507            && self.raw_sliced[raw_slice_idx + 1].source_idx <= source_slice.start
508        {
509            raw_slice_idx += 1;
510        }
511
512        // Find slice index of the end of this patch.
513        let mut slice_span = 1;
514        while raw_slice_idx + slice_span < self.raw_sliced.len()
515            && self.raw_sliced[raw_slice_idx + slice_span].source_idx < source_slice.end
516        {
517            slice_span += 1;
518        }
519
520        // Return the raw slices
521        self.raw_sliced[raw_slice_idx..(raw_slice_idx + slice_span)].to_vec()
522    }
523}
524
525/// Find the indices of all newlines in a string.
526pub fn iter_indices_of_newlines(raw_str: &str) -> impl Iterator<Item = usize> + '_ {
527    // TODO: This may be optimize-able by not doing it all up front.
528    raw_str.match_indices('\n').map(|(idx, _)| idx)
529}
530
531#[derive(Debug, PartialEq, Eq, Clone, Hash)]
532pub enum RawFileSliceType {
533    Comment,
534    BlockEnd,
535    BlockStart,
536    BlockMid,
537}
538
539/// A slice referring to a raw file.
540#[derive(Debug, PartialEq, Eq, Clone, Hash)]
541pub struct RawFileSlice {
542    /// Source string
543    raw: String,
544    pub(crate) slice_type: String,
545    /// Offset from beginning of source string
546    pub source_idx: usize,
547    slice_subtype: Option<RawFileSliceType>,
548    /// Block index, incremented on start or end block tags, e.g. "if", "for"
549    block_idx: usize,
550}
551
552impl RawFileSlice {
553    pub fn new(
554        raw: String,
555        slice_type: String,
556        source_idx: usize,
557        slice_subtype: Option<RawFileSliceType>,
558        block_idx: Option<usize>,
559    ) -> Self {
560        Self {
561            raw,
562            slice_type,
563            source_idx,
564            slice_subtype,
565            block_idx: block_idx.unwrap_or(0),
566        }
567    }
568}
569
570impl RawFileSlice {
571    /// Return the closing index of this slice.
572    fn end_source_idx(&self) -> usize {
573        self.source_idx + self.raw.len()
574    }
575
576    /// Return the a slice object for this slice.
577    pub fn source_slice(&self) -> Range<usize> {
578        self.source_idx..self.end_source_idx()
579    }
580
581    /// Based on its slice_type, does it only appear in the *source*?
582    /// There are some slice types which are automatically source only.
583    /// There are *also* some which are source only because they render
584    /// to an empty string.
585    fn is_source_only_slice(&self) -> bool {
586        // TODO: should any new logic go here?. Slice Type could probably go from String
587        // To Enum
588        matches!(
589            self.slice_type.as_str(),
590            "comment" | "block_end" | "block_start" | "block_mid"
591        )
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[test]
600    fn test_indices_of_newlines() {
601        vec![
602            ("", vec![]),
603            ("foo", vec![]),
604            ("foo\nbar", vec![3]),
605            ("\nfoo\n\nbar\nfoo\n\nbar\n", vec![0, 4, 5, 9, 13, 14, 18]),
606        ]
607        .into_iter()
608        .for_each(|(in_str, expected)| {
609            assert_eq!(
610                expected,
611                iter_indices_of_newlines(in_str).collect::<Vec<usize>>()
612            )
613        });
614    }
615
616    // const SIMPLE_SOURCE_STR: &str = "01234\n6789{{foo}}fo\nbarss";
617    // const SIMPLE_TEMPLATED_STR: &str = "01234\n6789x\nfo\nbarfss";
618
619    fn simple_sliced_file() -> Vec<TemplatedFileSlice> {
620        vec![
621            TemplatedFileSlice::new("literal", 0..10, 0..10),
622            TemplatedFileSlice::new("templated", 10..17, 10..12),
623            TemplatedFileSlice::new("literal", 17..25, 12..20),
624        ]
625    }
626
627    fn simple_raw_sliced_file() -> [RawFileSlice; 3] {
628        [
629            RawFileSlice::new("x".repeat(10), "literal".to_string(), 0, None, None),
630            RawFileSlice::new("x".repeat(7), "templated".to_string(), 10, None, None),
631            RawFileSlice::new("x".repeat(8), "literal".to_string(), 17, None, None),
632        ]
633    }
634
635    fn complex_sliced_file() -> Vec<TemplatedFileSlice> {
636        vec![
637            TemplatedFileSlice::new("literal", 0..13, 0..13),
638            TemplatedFileSlice::new("comment", 13..29, 13..13),
639            TemplatedFileSlice::new("literal", 29..44, 13..28),
640            TemplatedFileSlice::new("block_start", 44..68, 28..28),
641            TemplatedFileSlice::new("literal", 68..81, 28..41),
642            TemplatedFileSlice::new("templated", 81..86, 41..42),
643            TemplatedFileSlice::new("literal", 86..110, 42..66),
644            TemplatedFileSlice::new("templated", 68..86, 66..76),
645            TemplatedFileSlice::new("literal", 68..81, 76..89),
646            TemplatedFileSlice::new("templated", 81..86, 89..90),
647            TemplatedFileSlice::new("literal", 86..110, 90..114),
648            TemplatedFileSlice::new("templated", 68..86, 114..125),
649            TemplatedFileSlice::new("literal", 68..81, 125..138),
650            TemplatedFileSlice::new("templated", 81..86, 138..139),
651            TemplatedFileSlice::new("literal", 86..110, 139..163),
652            TemplatedFileSlice::new("templated", 110..123, 163..166),
653            TemplatedFileSlice::new("literal", 123..132, 166..175),
654            TemplatedFileSlice::new("block_end", 132..144, 175..175),
655            TemplatedFileSlice::new("literal", 144..155, 175..186),
656            TemplatedFileSlice::new("block_start", 155..179, 186..186),
657            TemplatedFileSlice::new("literal", 179..189, 186..196),
658            TemplatedFileSlice::new("templated", 189..194, 196..197),
659            TemplatedFileSlice::new("literal", 194..203, 197..206),
660            TemplatedFileSlice::new("literal", 179..189, 206..216),
661            TemplatedFileSlice::new("templated", 189..194, 216..217),
662            TemplatedFileSlice::new("literal", 194..203, 217..226),
663            TemplatedFileSlice::new("literal", 179..189, 226..236),
664            TemplatedFileSlice::new("templated", 189..194, 236..237),
665            TemplatedFileSlice::new("literal", 194..203, 237..246),
666            TemplatedFileSlice::new("block_end", 203..215, 246..246),
667            TemplatedFileSlice::new("literal", 215..230, 246..261),
668        ]
669    }
670
671    fn complex_raw_sliced_file() -> Vec<RawFileSlice> {
672        vec![
673            RawFileSlice::new(
674                "x".repeat(13).to_string(),
675                "literal".to_string(),
676                0,
677                None,
678                None,
679            ),
680            RawFileSlice::new(
681                "x".repeat(16).to_string(),
682                "comment".to_string(),
683                13,
684                None,
685                None,
686            ),
687            RawFileSlice::new(
688                "x".repeat(15).to_string(),
689                "literal".to_string(),
690                29,
691                None,
692                None,
693            ),
694            RawFileSlice::new(
695                "x".repeat(24).to_string(),
696                "block_start".to_string(),
697                44,
698                None,
699                None,
700            ),
701            RawFileSlice::new(
702                "x".repeat(13).to_string(),
703                "literal".to_string(),
704                68,
705                None,
706                None,
707            ),
708            RawFileSlice::new(
709                "x".repeat(5).to_string(),
710                "templated".to_string(),
711                81,
712                None,
713                None,
714            ),
715            RawFileSlice::new(
716                "x".repeat(24).to_string(),
717                "literal".to_string(),
718                86,
719                None,
720                None,
721            ),
722            RawFileSlice::new(
723                "x".repeat(13).to_string(),
724                "templated".to_string(),
725                110,
726                None,
727                None,
728            ),
729            RawFileSlice::new(
730                "x".repeat(9).to_string(),
731                "literal".to_string(),
732                123,
733                None,
734                None,
735            ),
736            RawFileSlice::new(
737                "x".repeat(12).to_string(),
738                "block_end".to_string(),
739                132,
740                None,
741                None,
742            ),
743            RawFileSlice::new(
744                "x".repeat(11).to_string(),
745                "literal".to_string(),
746                144,
747                None,
748                None,
749            ),
750            RawFileSlice::new(
751                "x".repeat(24).to_string(),
752                "block_start".to_string(),
753                155,
754                None,
755                None,
756            ),
757            RawFileSlice::new(
758                "x".repeat(10).to_string(),
759                "literal".to_string(),
760                179,
761                None,
762                None,
763            ),
764            RawFileSlice::new(
765                "x".repeat(5).to_string(),
766                "templated".to_string(),
767                189,
768                None,
769                None,
770            ),
771            RawFileSlice::new(
772                "x".repeat(9).to_string(),
773                "literal".to_string(),
774                194,
775                None,
776                None,
777            ),
778            RawFileSlice::new(
779                "x".repeat(12).to_string(),
780                "block_end".to_string(),
781                203,
782                None,
783                None,
784            ),
785            RawFileSlice::new(
786                "x".repeat(15).to_string(),
787                "literal".to_string(),
788                215,
789                None,
790                None,
791            ),
792        ]
793    }
794
795    struct FileKwargs {
796        f_name: String,
797        source_str: String,
798        templated_str: Option<String>,
799        sliced_file: Vec<TemplatedFileSlice>,
800        raw_sliced_file: Vec<RawFileSlice>,
801    }
802
803    fn simple_file_kwargs() -> FileKwargs {
804        FileKwargs {
805            f_name: "test.sql".to_string(),
806            source_str: "01234\n6789{{foo}}fo\nbarss".to_string(),
807            templated_str: Some("01234\n6789x\nfo\nbarss".to_string()),
808            sliced_file: simple_sliced_file().to_vec(),
809            raw_sliced_file: simple_raw_sliced_file().to_vec(),
810        }
811    }
812
813    fn complex_file_kwargs() -> FileKwargs {
814        FileKwargs {
815            f_name: "test.sql".to_string(),
816            source_str: complex_raw_sliced_file()
817                .iter()
818                .fold(String::new(), |acc, x| acc + &x.raw),
819            templated_str: None,
820            sliced_file: complex_sliced_file().to_vec(),
821            raw_sliced_file: complex_raw_sliced_file().to_vec(),
822        }
823    }
824
825    #[test]
826    /// Test TemplatedFile.get_line_pos_of_char_pos.
827    fn test_templated_file_get_line_pos_of_char_pos() {
828        let tests = [
829            (simple_file_kwargs(), 0, 1, 1),
830            (simple_file_kwargs(), 20, 3, 1),
831            (simple_file_kwargs(), 24, 3, 5),
832        ];
833
834        for test in tests {
835            let kwargs = test.0;
836
837            let tf = TemplatedFile::new(
838                kwargs.source_str,
839                kwargs.f_name,
840                kwargs.templated_str,
841                Some(kwargs.sliced_file),
842                Some(kwargs.raw_sliced_file),
843            )
844            .unwrap();
845
846            let (res_line_no, res_line_pos) = tf.get_line_pos_of_char_pos(test.1, true);
847
848            assert_eq!(res_line_no, test.2);
849            assert_eq!(res_line_pos, test.3);
850        }
851    }
852
853    #[test]
854    fn test_templated_file_find_slice_indices_of_templated_pos() {
855        let tests = vec![
856            // "templated_position,inclusive,file_slices,sliced_idx_start,sliced_idx_stop",
857            // TODO Fix these
858            // (100, true, complex_file_kwargs(), 10, 11),
859            // (13, true, complex_file_kwargs(), 0, 3),
860            // (28, true, complex_file_kwargs(), 2, 5),
861            // # Check end slicing.
862            (12, true, simple_file_kwargs(), 1, 3),
863            (20, true, simple_file_kwargs(), 2, 3),
864            // Check inclusivity
865            // (13, false, complex_file_kwargs(), 0, 1),
866        ];
867
868        for test in tests {
869            let args = test.2;
870
871            let file = TemplatedFile::new(
872                args.source_str,
873                args.f_name,
874                args.templated_str,
875                Some(args.sliced_file),
876                Some(args.raw_sliced_file),
877            )
878            .unwrap();
879
880            let (res_start, res_stop) = file
881                .find_slice_indices_of_templated_pos(test.0, None, Some(test.1))
882                .unwrap();
883
884            assert_eq!(res_start, test.3);
885            assert_eq!(res_stop, test.4);
886        }
887    }
888
889    #[test]
890    /// Test TemplatedFile.templated_slice_to_source_slice
891    fn test_templated_file_templated_slice_to_source_slice() {
892        let test_cases = vec![
893            // Simple example
894            (
895                5..10,
896                5..10,
897                true,
898                FileKwargs {
899                    sliced_file: vec![TemplatedFileSlice::new("literal", 0..20, 0..20)],
900                    raw_sliced_file: vec![RawFileSlice::new(
901                        "x".repeat(20),
902                        "literal".to_string(),
903                        0,
904                        None,
905                        None,
906                    )],
907                    source_str: "x".repeat(20),
908                    f_name: "foo.sql".to_string(),
909                    templated_str: None,
910                },
911            ),
912            // Trimming the end of a literal (with things that follow).
913            (10..13, 10..13, true, complex_file_kwargs()),
914            // // Unrealistic, but should still work
915            (
916                5..10,
917                55..60,
918                true,
919                FileKwargs {
920                    sliced_file: vec![TemplatedFileSlice::new("literal", 50..70, 0..20)],
921                    raw_sliced_file: vec![
922                        RawFileSlice::new("x".repeat(50), "literal".to_string(), 0, None, None),
923                        RawFileSlice::new("x".repeat(20), "literal".to_string(), 50, None, None),
924                    ],
925                    source_str: "x".repeat(70),
926                    f_name: "foo.sql".to_string(),
927                    templated_str: None,
928                },
929            ),
930            // // Spanning a template
931            (5..15, 5..20, false, simple_file_kwargs()),
932            // // Handling templated
933            (
934                5..15,
935                0..25,
936                false,
937                FileKwargs {
938                    sliced_file: simple_file_kwargs()
939                        .sliced_file
940                        .iter()
941                        .map(|slc| {
942                            TemplatedFileSlice::new(
943                                "templated",
944                                slc.source_slice.clone(),
945                                slc.templated_slice.clone(),
946                            )
947                        })
948                        .collect(),
949                    raw_sliced_file: simple_file_kwargs()
950                        .raw_sliced_file
951                        .iter()
952                        .map(|slc| {
953                            RawFileSlice::new(
954                                slc.raw.to_string(),
955                                "templated".to_string(),
956                                slc.source_idx,
957                                None,
958                                None,
959                            )
960                        })
961                        .collect(),
962                    ..simple_file_kwargs()
963                },
964            ),
965            // // Handling single length slices
966            (10..10, 10..10, true, simple_file_kwargs()),
967            (12..12, 17..17, true, simple_file_kwargs()),
968            // // Dealing with single length elements
969            (
970                20..20,
971                25..25,
972                true,
973                FileKwargs {
974                    sliced_file: simple_file_kwargs()
975                        .sliced_file
976                        .into_iter()
977                        .chain(vec![TemplatedFileSlice::new("comment", 25..35, 20..20)])
978                        .collect(),
979                    raw_sliced_file: simple_file_kwargs()
980                        .raw_sliced_file
981                        .into_iter()
982                        .chain(vec![RawFileSlice::new(
983                            "x".repeat(10),
984                            "comment".to_string(),
985                            25,
986                            None,
987                            None,
988                        )])
989                        .collect(),
990                    source_str: simple_file_kwargs().source_str.to_string() + &"x".repeat(10),
991                    ..simple_file_kwargs()
992                },
993            ),
994            // // Just more test coverage
995            (43..43, 87..87, true, complex_file_kwargs()),
996            (13..13, 13..13, true, complex_file_kwargs()),
997            (186..186, 155..155, true, complex_file_kwargs()),
998            // Backward slicing.
999            (
1000                100..130,
1001                // NB This actually would reference the wrong way around if we
1002                // just take the points. Here we should handle it gracefully.
1003                68..110,
1004                false,
1005                complex_file_kwargs(),
1006            ),
1007        ];
1008
1009        for (in_slice, out_slice, is_literal, tf_kwargs) in test_cases {
1010            let file = TemplatedFile::new(
1011                tf_kwargs.source_str,
1012                tf_kwargs.f_name,
1013                tf_kwargs.templated_str,
1014                Some(tf_kwargs.sliced_file),
1015                Some(tf_kwargs.raw_sliced_file),
1016            )
1017            .unwrap();
1018
1019            let source_slice = file.templated_slice_to_source_slice(in_slice).unwrap();
1020            let literal_test = file.is_source_slice_literal(&source_slice);
1021
1022            assert_eq!((is_literal, source_slice), (literal_test, out_slice));
1023        }
1024    }
1025
1026    #[test]
1027    /// Test TemplatedFile.source_only_slices
1028    fn test_templated_file_source_only_slices() {
1029        let test_cases = vec![
1030            // Comment example
1031            (
1032                TemplatedFile::new(
1033                    format!("{}{}{}", "a".repeat(10), "{# b #}", "a".repeat(10)),
1034                    "test".to_string(),
1035                    None,
1036                    Some(vec![
1037                        TemplatedFileSlice::new("literal", 0..10, 0..10),
1038                        TemplatedFileSlice::new("templated", 10..17, 10..10),
1039                        TemplatedFileSlice::new("literal", 17..27, 10..20),
1040                    ]),
1041                    Some(vec![
1042                        RawFileSlice::new(
1043                            "a".repeat(10).to_string(),
1044                            "literal".to_string(),
1045                            0,
1046                            None,
1047                            None,
1048                        ),
1049                        RawFileSlice::new(
1050                            "{# b #}".to_string(),
1051                            "comment".to_string(),
1052                            10,
1053                            None,
1054                            None,
1055                        ),
1056                        RawFileSlice::new(
1057                            "a".repeat(10).to_string(),
1058                            "literal".to_string(),
1059                            17,
1060                            None,
1061                            None,
1062                        ),
1063                    ]),
1064                )
1065                .unwrap(),
1066                vec![RawFileSlice::new(
1067                    "{# b #}".to_string(),
1068                    "comment".to_string(),
1069                    10,
1070                    None,
1071                    None,
1072                )],
1073            ),
1074            // Template tags aren't source only.
1075            (
1076                TemplatedFile::new(
1077                    "aaa{{ b }}aaa".to_string(),
1078                    "test".to_string(),
1079                    None,
1080                    Some(vec![
1081                        TemplatedFileSlice::new("literal", 0..3, 0..3),
1082                        TemplatedFileSlice::new("templated", 3..10, 3..6),
1083                        TemplatedFileSlice::new("literal", 10..13, 6..9),
1084                    ]),
1085                    Some(vec![
1086                        RawFileSlice::new("aaa".to_string(), "literal".to_string(), 0, None, None),
1087                        RawFileSlice::new(
1088                            "{{ b }}".to_string(),
1089                            "templated".to_string(),
1090                            3,
1091                            None,
1092                            None,
1093                        ),
1094                        RawFileSlice::new("aaa".to_string(), "literal".to_string(), 10, None, None),
1095                    ]),
1096                )
1097                .unwrap(),
1098                vec![],
1099            ),
1100        ];
1101
1102        for (file, expected) in test_cases {
1103            assert_eq!(file.source_only_slices(), expected, "Failed for {:?}", file);
1104        }
1105    }
1106}