sql_dialect_fmt_formatter/
range.rs1use std::ops::Range;
14
15use sql_dialect_fmt_syntax::{SyntaxKind, SyntaxNode};
16
17use crate::{format, FormatOptions};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct RangeEdit {
23 pub range: Range<usize>,
25 pub new_text: String,
27}
28
29pub fn format_range(
36 source: &str,
37 target: Range<usize>,
38 options: &FormatOptions,
39) -> Option<RangeEdit> {
40 let parse = sql_dialect_fmt_parser::parse_with_dialect(source, options.dialect);
43 if !parse.errors().is_empty() {
44 return None;
45 }
46
47 let statements = collect_statements(&parse.syntax());
48 let mut selected = statements
49 .iter()
50 .filter(|statement| statement.intersects(&target));
51 let first = selected.next()?;
52 let last = selected.next_back().unwrap_or(first);
53
54 let block_range = first.sig_start..last.full_end;
55 let block = source.get(block_range.clone())?;
56 let formatted = format(block, options);
57 let new_text = strip_one_trailing_newline(&formatted);
61
62 if new_text == block {
63 return None;
64 }
65 Some(RangeEdit {
66 range: block_range,
67 new_text: new_text.to_string(),
68 })
69}
70
71struct StatementSpan {
74 sig_start: usize,
75 full_end: usize,
76}
77
78impl StatementSpan {
79 fn intersects(&self, target: &Range<usize>) -> bool {
82 self.sig_start <= target.end && target.start <= self.full_end
83 }
84}
85
86fn collect_statements(root: &SyntaxNode) -> Vec<StatementSpan> {
87 let mut spans = Vec::new();
88 let mut pending: Option<(usize, usize)> = None; for element in root.children_with_tokens() {
91 if let Some(node) = element.as_node() {
92 if let Some((sig_start, node_end)) = pending.take() {
93 spans.push(StatementSpan {
94 sig_start,
95 full_end: node_end,
96 });
97 }
98 let node_start = usize::from(node.text_range().start());
99 let node_end = usize::from(node.text_range().end());
100 pending = Some((significant_start(node).unwrap_or(node_start), node_end));
101 } else if let Some(token) = element.as_token() {
102 if token.kind() == SyntaxKind::SEMICOLON {
103 if let Some((sig_start, _node_end)) = pending.take() {
104 spans.push(StatementSpan {
105 sig_start,
106 full_end: usize::from(token.text_range().end()),
107 });
108 }
109 }
110 }
111 }
112 if let Some((sig_start, node_end)) = pending.take() {
113 spans.push(StatementSpan {
114 sig_start,
115 full_end: node_end,
116 });
117 }
118 spans
119}
120
121fn significant_start(node: &SyntaxNode) -> Option<usize> {
124 node.descendants_with_tokens()
125 .filter_map(|element| element.into_token())
126 .find(|token| !token.kind().is_trivia())
127 .map(|token| usize::from(token.text_range().start()))
128}
129
130fn strip_one_trailing_newline(text: &str) -> &str {
131 text.strip_suffix("\r\n")
132 .or_else(|| text.strip_suffix('\n'))
133 .unwrap_or(text)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 fn apply(source: &str, edit: &RangeEdit) -> String {
141 let mut out = String::new();
142 out.push_str(&source[..edit.range.start]);
143 out.push_str(&edit.new_text);
144 out.push_str(&source[edit.range.end..]);
145 out
146 }
147
148 fn range_format(source: &str, target: Range<usize>) -> Option<String> {
149 format_range(source, target, &FormatOptions::default()).map(|edit| apply(source, &edit))
150 }
151
152 #[test]
153 fn reformats_only_the_selected_statement() {
154 let source = "select 1 ;\n\n\nselect a,b from t;\n\nSELECT 3;\n";
155 let start = source.find("select a").unwrap();
157 let out = range_format(source, start..start + 5).expect("edit");
158 assert_eq!(out, "select 1 ;\n\n\nSELECT a, b\nFROM t;\n\nSELECT 3;\n");
159 assert!(out.starts_with("select 1 ;\n\n\n"));
161 assert!(out.ends_with("\n\nSELECT 3;\n"));
162 }
163
164 #[test]
165 fn keeps_same_line_trailing_comment_attached() {
166 let source = "select a,b from t; -- keep me\nselect 2;\n";
167 let out = range_format(source, 0..3).expect("edit");
168 assert_eq!(out, "SELECT a, b\nFROM t; -- keep me\nselect 2;\n");
169 }
170
171 #[test]
172 fn full_range_matches_whole_document_format() {
173 let source = "select 1;\nselect 2;\n";
174 let out = range_format(source, 0..source.len()).expect("edit");
175 assert_eq!(out, format(source, &FormatOptions::default()));
176 }
177
178 #[test]
179 fn cursor_between_statements_makes_no_edit() {
180 let source = "select 1;\n\nselect 2;\n";
181 let gap = source.find("\n\n").unwrap() + 1;
183 assert_eq!(
184 format_range(source, gap..gap, &FormatOptions::default()),
185 None
186 );
187 }
188
189 #[test]
190 fn already_formatted_selection_makes_no_edit() {
191 let source = "SELECT 1;\nSELECT a, b\nFROM t;\n";
192 assert_eq!(format_range(source, 0..8, &FormatOptions::default()), None);
193 }
194
195 #[test]
196 fn unparseable_source_makes_no_edit() {
197 let source = "ALTER TABLE t ADD COLUMN c INT;\n";
198 assert_eq!(format_range(source, 0..5, &FormatOptions::default()), None);
199 }
200
201 #[test]
202 fn range_formatting_is_idempotent() {
203 let source = "select 1 ;\n\nselect a,b from t;\n";
204 let start = source.find("select a").unwrap();
205 let once = range_format(source, start..start + 5).expect("edit");
206 let new_start = once.find("SELECT a").unwrap();
208 assert_eq!(
209 format_range(&once, new_start..new_start + 5, &FormatOptions::default()),
210 None
211 );
212 }
213
214 #[test]
215 fn selection_spanning_two_statements_reformats_both() {
216 let source = "select 1;select 2;\nSELECT 3;\n";
217 let out = range_format(source, 0..12).expect("edit");
218 assert_eq!(out, "SELECT 1;\nSELECT 2;\nSELECT 3;\n");
219 }
220}