Skip to content

Commit 5291a1a

Browse files
fix: add missing scope boundaries (#709)
1 parent 24d927b commit 5291a1a

3 files changed

Lines changed: 117 additions & 2 deletions

File tree

crates/pgls_treesitter/src/context/ancestors.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ pub struct Scope {
33
pub ancestors: AncestorTracker,
44
}
55

6-
static SCOPE_BOUNDARIES: &[&str] = &["statement", "ERROR", "program"];
6+
static SCOPE_BOUNDARIES: &[&str] = &[
7+
"statement",
8+
"ERROR",
9+
"program",
10+
"block",
11+
"transaction",
12+
"psql_meta_command",
13+
"copy_data_stream",
14+
];
715

816
#[derive(Debug)]
917
pub struct ScopeTracker {
@@ -22,7 +30,7 @@ impl ScopeTracker {
2230

2331
self.scopes
2432
.last_mut()
25-
.unwrap_or_else(|| panic!("Unhandled node kind: {}", node.kind()))
33+
.unwrap_or_else(|| panic!("No top-level grammar-rule found. Please create an issue with the entire Postgres file, noting cursor/hover position."))
2634
.ancestors
2735
.register(node, position);
2836
}
@@ -105,3 +113,104 @@ impl AncestorTracker {
105113
true
106114
}
107115
}
116+
117+
#[cfg(test)]
118+
mod tests {
119+
use crate::context::{TreeSitterContextParams, TreesitterContext};
120+
121+
fn get_tree(input: &str) -> tree_sitter::Tree {
122+
let mut parser = tree_sitter::Parser::new();
123+
parser
124+
.set_language(&pgls_treesitter_grammar::LANGUAGE.into())
125+
.expect("Couldn't set language");
126+
127+
parser.parse(input, None).expect("Unable to parse tree")
128+
}
129+
130+
fn assert_no_panic_for_all_positions(sql: &str) {
131+
let tree = get_tree(sql);
132+
for pos in 0..sql.len() {
133+
let params = TreeSitterContextParams {
134+
position: (pos as u32).into(),
135+
text: sql,
136+
tree: &tree,
137+
};
138+
let _ = TreesitterContext::new(params);
139+
}
140+
}
141+
142+
#[test]
143+
fn scope_boundary_block() {
144+
assert_no_panic_for_all_positions("BEGIN; SELECT 1; END;");
145+
}
146+
147+
#[test]
148+
fn scope_boundary_transaction() {
149+
assert_no_panic_for_all_positions("BEGIN TRANSACTION; SELECT 1; COMMIT;");
150+
assert_no_panic_for_all_positions("BEGIN; INSERT INTO t VALUES (1); ROLLBACK;");
151+
}
152+
153+
#[test]
154+
fn scope_boundary_psql_meta_command() {
155+
assert_no_panic_for_all_positions("\\dt\n\\d users");
156+
}
157+
158+
#[test]
159+
fn scope_boundary_copy_data_stream() {
160+
assert_no_panic_for_all_positions("COPY t FROM STDIN;\n1\tAlice\n\\.\n");
161+
}
162+
163+
#[test]
164+
fn scope_boundary_comment() {
165+
assert_no_panic_for_all_positions("-- a comment\nSELECT 1;");
166+
}
167+
168+
#[test]
169+
fn issue_704_regression() {
170+
let statements = vec![
171+
r#"
172+
CREATE OR REPLACE FUNCTION my_schema.my_function1(
173+
pi_1 character varying,
174+
pi_2 character varying,
175+
pi_3 jsonb,
176+
OUT po_1 integer,
177+
OUT po_2 integer,
178+
OUT result integer
179+
)
180+
RETURNS record
181+
LANGUAGE plpgsql
182+
AS $function$
183+
"#
184+
.trim(),
185+
186+
r#"
187+
CREATE OR REPLACE FUNCTION my_schema.my_function2(
188+
pi_1 character varying,
189+
pi_2 character varying,
190+
pi_3 jsonb,
191+
OUT po_1 integer,
192+
OUT po_2 integer,
193+
OUT result integer
194+
)
195+
RETURNS record
196+
LANGUAGE plpgsql
197+
AS $function$
198+
DECLARE
199+
BEGIN
200+
-- Function logic goes here
201+
-- For example, you can perform some operations using the input parameters and set the output parameters accordingly
202+
203+
-- Example logic (replace with actual implementation):
204+
po_1 := length(pi_1); -- Set po_1 to the length of pi_1
205+
po_2 := length(pi_2); -- Set po_2 to the length of pi_2
206+
result := po_1 + po_2; -- Set result to the sum of po_1 and po_2
207+
END;
208+
$function$;
209+
"#.trim(),
210+
];
211+
212+
for stmt in statements {
213+
assert_no_panic_for_all_positions(stmt);
214+
}
215+
}
216+
}

crates/pgls_treesitter/src/context/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,10 @@ impl<'a> TreesitterContext<'a> {
305305
let parent_node_kind = parent_node.kind();
306306
let current_node_kind = current_node.kind();
307307

308+
if ["comment", "marginalia"].contains(&current_node_kind) {
309+
return;
310+
}
311+
308312
self.scope_tracker.register(current_node, self.position);
309313

310314
// prevent infinite recursion – this can happen with ERROR nodes

crates/pgls_treesitter_grammar/grammar.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ module.exports = grammar({
8484
rules: {
8585
program: ($) =>
8686
choice(
87+
// NOTE: if you add a new top-level statement, make sure to define it
88+
// as a top-level boundary in treesitter context
8789
seq(
8890
repeat(
8991
choice(

0 commit comments

Comments
 (0)