Skip to content

Commit 78872a3

Browse files
committed
temp [ci skip]
1 parent b2aae72 commit 78872a3

File tree

10 files changed

+145
-71
lines changed

10 files changed

+145
-71
lines changed

crates/lib-core/src/parser/segments/base.rs

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ impl SegmentBuilder {
4141
SegmentBuilder::token(id, raw, SyntaxKind::Symbol).finish()
4242
}
4343

44+
pub fn raw(id: u32, raw: &str) -> ErasedSegment {
45+
SegmentBuilder::token(id, raw, SyntaxKind::Raw).finish()
46+
}
47+
4448
pub fn node(
4549
id: u32,
4650
syntax_kind: SyntaxKind,

crates/lib/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ name = "depth_map"
3838
harness = false
3939

4040
[features]
41+
default = ["python"]
4142
python = ["pyo3", "sqruff-lib-core/serde"]
4243

4344
[dependencies]

crates/lib/src/core/linter/linted_file.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use sqruff_lib_core::errors::{SQLBaseError, SqlError};
77
use sqruff_lib_core::parser::segments::fix::FixPatch;
88
use sqruff_lib_core::templaters::base::{RawFileSlice, TemplatedFile};
99

10-
#[derive(Debug, Default)]
10+
#[derive(Debug, Default, Clone)]
1111
pub struct LintedFile {
1212
pub path: String,
1313
pub patches: Vec<FixPatch>,

crates/lib/src/core/rules/base.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ pub trait Rule: CloneRule + dyn_clone::DynClone + Debug + 'static + Send + Sync
181181
tree: ErasedSegment,
182182
config: &FluffConfig,
183183
) -> Vec<SQLLintError> {
184-
let mut root_context = RuleContext::new(tables, dialect, config, tree.clone());
184+
let mut root_context =
185+
RuleContext::new(tables, dialect, config, tree.clone(), &templated_file);
185186
let mut vs = Vec::new();
186187

187188
// TODO Will to return a note that rules were skipped

crates/lib/src/core/rules/context.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::core::config::FluffConfig;
1313
pub struct RuleContext<'a> {
1414
pub tables: &'a Tables,
1515
pub dialect: &'a Dialect,
16-
pub templated_file: Option<TemplatedFile>,
16+
pub templated_file: &'a TemplatedFile,
1717
pub path: Option<String>,
1818
pub config: &'a FluffConfig,
1919

@@ -41,13 +41,14 @@ impl<'a> RuleContext<'a> {
4141
dialect: &'a Dialect,
4242
config: &'a FluffConfig,
4343
segment: ErasedSegment,
44+
templated_file: &'a TemplatedFile,
4445
) -> Self {
4546
Self {
4647
tables,
4748
dialect,
4849
config,
4950
segment,
50-
templated_file: <_>::default(),
51+
templated_file,
5152
path: <_>::default(),
5253
parent_stack: <_>::default(),
5354
raw_stack: <_>::default(),

crates/lib/src/rules.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ pub mod aliasing;
77
pub mod ambiguous;
88
pub mod capitalisation;
99
pub mod convention;
10+
pub mod jinja;
1011
pub mod layout;
1112
pub mod references;
1213
pub mod structure;
13-
pub mod jinja;
1414

1515
pub fn rules() -> Vec<ErasedRule> {
1616
chain!(

crates/lib/src/rules/jinja/jj01.rs

+126-62
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use ahash::AHashMap;
22
use sqruff_lib_core::lint_fix::LintFix;
3-
use sqruff_lib_core::parser::segments::base::ErasedSegment;
3+
use sqruff_lib_core::parser::segments::base::{ErasedSegment, SegmentBuilder};
44
use sqruff_lib_core::parser::segments::fix::SourceFix;
55

66
use crate::core::config::Value;
@@ -53,31 +53,28 @@ SELECT {{ a }} from {{
5353
"#
5454
}
5555

56-
5756
fn groups(&self) -> &'static [RuleGroups] {
5857
&[RuleGroups::All, RuleGroups::Jinja]
5958
}
6059

6160
fn eval(&self, context: &RuleContext) -> Vec<LintResult> {
6261
debug_assert!(context.segment.get_position_marker().is_some());
63-
62+
6463
// If the position marker for the root segment is literal then there's
6564
// no templated code, so return early
6665
if context.segment.get_position_marker().unwrap().is_literal() {
6766
return vec![];
6867
}
6968

70-
// Need templated file to proceed
71-
let Some(templated_file) = &context.templated_file else {
72-
return vec![];
73-
};
74-
7569
let mut results: Vec<LintResult> = vec![];
7670

7771
// Work through the templated slices
78-
for raw_slice in templated_file.raw_sliced_iter() {
72+
for raw_slice in context.templated_file.raw_sliced_iter() {
7973
// Only want templated slices
80-
if !matches!(raw_slice.slice_type.as_str(), "templated" | "block_start" | "block_end") {
74+
if !matches!(
75+
raw_slice.slice_type.as_str(),
76+
"templated" | "block_start" | "block_end"
77+
) {
8178
continue;
8279
}
8380

@@ -88,8 +85,13 @@ SELECT {{ a }} from {{
8885

8986
// Partition and position
9087
let src_idx = raw_slice.source_idx;
91-
let (tag_pre, ws_pre, inner, ws_post, tag_post) = Self::get_white_space_ends(stripped.to_string());
92-
let position = raw_slice.raw.find(stripped.chars().next().unwrap()).unwrap_or(0);
88+
let (tag_pre, ws_pre, inner, ws_post, tag_post) =
89+
Self::get_white_space_ends(stripped.to_string());
90+
91+
let position = raw_slice
92+
.raw
93+
.find(stripped.chars().next().unwrap())
94+
.unwrap_or(0);
9395

9496
// Whitespace should be single space OR contain newline
9597
let mut pre_fix = None;
@@ -117,7 +119,8 @@ SELECT {{ a }} from {{
117119
);
118120

119121
// Find raw segment to attach fix to
120-
let Some(raw_seg) = Self::find_raw_at_src_index(context.segment.clone(), src_idx) else {
122+
let Some(raw_seg) = Self::find_raw_at_src_index(&context.segment, src_idx)
123+
else {
121124
continue;
122125
};
123126

@@ -126,39 +129,35 @@ SELECT {{ a }} from {{
126129
continue;
127130
}
128131

129-
let source_fixes = vec![LintFix::replace(
130-
raw_seg.clone(),
131-
vec![],
132-
Some(vec![SourceFix::new(
133-
fixed.into(),
134-
src_idx + position..src_idx + position + stripped.len(),
135-
// This position in the templated file is rough, but close enough for sequencing.
136-
raw_seg.get_position_marker().unwrap().templated_slice.clone(),
137-
).erased()]),
132+
let ps_marker = raw_seg
133+
.get_position_marker()
134+
.map(|pm| pm.templated_slice.clone());
135+
let Some(ps_marker) = ps_marker else {
136+
continue;
137+
};
138+
139+
let source_fixes = vec![SourceFix::new(
140+
fixed.clone().into(),
141+
src_idx + position..src_idx + position + stripped.len(),
142+
ps_marker,
138143
)];
139144

140145
results.push(LintResult::new(
141146
Some(raw_seg.clone()),
142-
source_fixes,
143-
Some(format!("Jinja tags should have a single whitespace on either side: {}", stripped)),
147+
vec![LintFix::replace(
148+
raw_seg.clone(),
149+
vec![raw_seg.edit(raw_seg.id(), fixed.into(), Some(source_fixes))],
150+
None,
151+
)],
152+
Some(format!(
153+
"Jinja tags should have a single whitespace on either side: {}",
154+
stripped
155+
)),
144156
None,
145157
));
146158
}
147159

148-
// results.push(LintResult::new(
149-
// Some(raw_seg.clone()),
150-
// vec![LintFix::replace(
151-
// raw_seg,
152-
// [raw_seg.edit(source_fixes)],
153-
// None,
154-
// )],
155-
// Some(format!("Jinja tags should have a single whitespace on either side: {}", stripped)),
156-
// raw_seg.get_position_marker().unwrap().source_slice,
157-
// ));
158-
// }
159-
// results
160-
161-
unimplemented!();
160+
results
162161
}
163162

164163
fn is_fix_compatible(&self) -> bool {
@@ -167,27 +166,34 @@ SELECT {{ a }} from {{
167166

168167
fn crawl_behaviour(&self) -> Crawler {
169168
RootOnlyCrawler.into()
170-
}
169+
}
171170
}
172171

173172
impl RuleJJ01 {
174173
fn get_white_space_ends(s: String) -> (String, String, String, String, String) {
175-
assert!(s.starts_with('{') && s.ends_with('}'), "String must start with {{ and end with }}");
174+
assert!(
175+
s.starts_with('{') && s.ends_with('}'),
176+
"String must start with {{ and end with }}"
177+
);
176178

177179
// Get the main content between the tag markers
178-
let mut main = s[2..s.len()-2].to_string();
180+
let mut main = s[2..s.len() - 2].to_string();
179181
let mut pre = s[..2].to_string();
180-
let mut post = s[s.len()-2..].to_string();
182+
let mut post = s[s.len() - 2..].to_string();
181183

182184
// Handle plus/minus modifiers
183185
let modifier_chars = ['+', '-'];
184186
if !main.is_empty() && modifier_chars.contains(&main.chars().next().unwrap()) {
187+
let first_char = main.chars().next().unwrap();
185188
main = main[1..].to_string();
186-
pre = s[..3].to_string();
189+
// Keep the modifier directly after {% or {{
190+
pre = format!("{}{}", pre, first_char);
187191
}
188192
if !main.is_empty() && modifier_chars.contains(&main.chars().last().unwrap()) {
189-
main = main[..main.len()-1].to_string();
190-
post = s[s.len()-3..].to_string();
193+
let last_char = main.chars().last().unwrap();
194+
main = main[..main.len() - 1].to_string();
195+
// Keep the modifier directly before %} or }}
196+
post = format!("{}{}", last_char, post);
191197
}
192198

193199
// Split out inner content and surrounding whitespace
@@ -199,7 +205,7 @@ impl RuleJJ01 {
199205
(pre, pre_ws, inner, post_ws, post)
200206
}
201207

202-
fn find_raw_at_src_index(segment: ErasedSegment, src_idx: usize) -> Option<ErasedSegment> {
208+
fn find_raw_at_src_index(segment: &ErasedSegment, src_idx: usize) -> Option<&ErasedSegment> {
203209
// Recursively search to find a raw segment for a position in the source.
204210
// NOTE: This assumes it's not being called on a `raw`.
205211
// In the case that there are multiple potential targets, we will find the first.
@@ -208,23 +214,81 @@ impl RuleJJ01 {
208214
assert!(segments.len() > 0, "Segment must have segments");
209215

210216
for seg in segments {
211-
if let Some(pos_marker) = seg.get_position_marker() {
212-
let src_slice = pos_marker.source_slice.clone();
213-
214-
// If it's before, skip onward
215-
if src_slice.end <= src_idx {
216-
continue;
217-
}
218-
219-
// Is the current segment raw?
220-
if seg.is_raw() {
221-
return Some(seg.clone());
222-
}
223-
224-
// Otherwise recurse
225-
return Self::find_raw_at_src_index(seg.clone(), src_idx);
217+
let Some(pos_marker) = seg.get_position_marker() else {
218+
continue;
219+
};
220+
// If it's before, skip onward
221+
if pos_marker.source_slice.end <= src_idx {
222+
continue;
223+
}
224+
// Is the current segment raw?
225+
if seg.is_raw() {
226+
return Some(seg);
226227
}
228+
// Otherwise recurse
229+
return Self::find_raw_at_src_index(seg, src_idx);
227230
}
228231
None
229232
}
230-
}
233+
}
234+
235+
#[cfg(test)]
236+
mod tests {
237+
use super::*;
238+
use crate::{
239+
core::{config::FluffConfig, linter::core::Linter},
240+
templaters::jinja::JinjaTemplater,
241+
};
242+
243+
#[test]
244+
fn test_get_white_space_ends() {
245+
let cases = vec![
246+
(
247+
"{{+ my_content }}",
248+
(
249+
"{{+".to_string(),
250+
" ".to_string(),
251+
"my_content".to_string(),
252+
" ".to_string(),
253+
"}}".to_string(),
254+
),
255+
),
256+
(
257+
"{%+if true-%}",
258+
(
259+
"{%+".to_string(),
260+
"".to_string(),
261+
"if true".to_string(),
262+
"".to_string(),
263+
"-%}".to_string(),
264+
),
265+
),
266+
];
267+
268+
for (input, expected) in cases {
269+
let result = RuleJJ01::get_white_space_ends(input.to_string());
270+
assert_eq!(result, expected);
271+
}
272+
}
273+
274+
#[test]
275+
fn test_simple_example() {
276+
let start = "SELECT 1 from {%+if true-%} {{ref('foo')}} {%-endif%}".to_string();
277+
let want = "SELECT 1 from {%+ if true -%} {{ ref('foo') }} {%- endif %}".to_string();
278+
279+
let config = FluffConfig::from_source(
280+
r#"
281+
[sqruff]
282+
rules = JJ01
283+
templater = jinja
284+
"#,
285+
None,
286+
);
287+
288+
let mut linter = Linter::new(config, None, Some(&JinjaTemplater), false);
289+
let result = linter.lint_string_wrapped(&start, None, true);
290+
291+
let fixed = result.paths[0].files[0].clone().fix_string();
292+
assert_eq!(fixed, want, "\nExpected: {}\nGot: {}", want, fixed);
293+
}
294+
}

crates/lib/src/rules/layout/lt13.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Start file on either code or comment. (The ^ represents the beginning of the fil
105105
}
106106

107107
let raw_stack =
108-
Segments::from_vec(raw_segments.clone(), context.templated_file.clone());
108+
Segments::from_vec(raw_segments.clone(), Some(context.templated_file.clone()));
109109
// Non-whitespace segment.
110110
if !raw_stack.all(Some(|seg| seg.is_meta())) {
111111
return vec![LintResult::new(

crates/lib/src/utils/functional/context.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ impl<'a> FunctionalContext<'a> {
1414
pub fn segment(&self) -> Segments {
1515
Segments::new(
1616
self.context.segment.clone(),
17-
self.context.templated_file.clone(),
17+
Some(self.context.templated_file.clone()),
1818
)
1919
}
2020

2121
pub fn siblings_post(&self) -> Segments {
2222
Segments::from_vec(
2323
self.context.siblings_post(),
24-
self.context.templated_file.clone(),
24+
Some(self.context.templated_file.clone()),
2525
)
2626
}
2727

2828
pub fn parent_stack(&self) -> Segments {
2929
Segments::from_vec(
3030
self.context.parent_stack.clone(),
31-
self.context.templated_file.clone(),
31+
Some(self.context.templated_file.clone()),
3232
)
3333
}
3434
}

0 commit comments

Comments
 (0)