From c03bdc5aafeaa78df211c9aebd46fee73c44a5c5 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 27 Dec 2025 11:23:45 -0500 Subject: [PATCH 1/2] ide: add hover for create function --- crates/squawk_ide/src/binder.rs | 2 +- crates/squawk_ide/src/hover.rs | 132 +++++++++++++++++++++++++++++++ crates/squawk_ide/src/resolve.rs | 64 +++++---------- 3 files changed, 154 insertions(+), 44 deletions(-) diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index ed973464..cc5c65c5 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -141,7 +141,7 @@ fn bind_create_function(b: &mut Binder, create_function: ast::CreateFunction) { let name_ptr = path_to_ptr(&path); - let Some(schema) = b.current_search_path().first().cloned() else { + let Some(schema) = schema_name(b, &path, false) else { return; }; diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index be9ab14d..a0b0050b 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -22,6 +22,10 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { if is_index_ref(&name_ref) { return hover_index(file, &name_ref, &binder); } + + if is_function_ref(&name_ref) { + return hover_function(file, &name_ref, &binder); + } } if let Some(name) = ast::Name::cast(parent) { @@ -38,6 +42,11 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { if let Some(create_index) = name.syntax().ancestors().find_map(ast::CreateIndex::cast) { return format_create_index(&create_index, &binder); } + + if let Some(create_function) = name.syntax().ancestors().find_map(ast::CreateFunction::cast) + { + return format_create_function(&create_function, &binder); + } } None @@ -278,6 +287,65 @@ fn is_index_ref(name_ref: &ast::NameRef) -> bool { false } +fn is_function_ref(name_ref: &ast::NameRef) -> bool { + for ancestor in name_ref.syntax().ancestors() { + if ast::DropFunction::can_cast(ancestor.kind()) { + return true; + } + } + false +} + +fn hover_function( + file: &ast::SourceFile, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let function_ptr = resolve::resolve_name_ref(binder, name_ref)?; + + let root = file.syntax(); + let function_name_node = function_ptr.to_node(root); + + let create_function = function_name_node + .ancestors() + .find_map(ast::CreateFunction::cast)?; + + format_create_function(&create_function, binder) +} + +fn format_create_function( + create_function: &ast::CreateFunction, + binder: &binder::Binder, +) -> Option { + let path = create_function.path()?; + let segment = path.segment()?; + let name = segment.name()?; + let function_name = name.syntax().text().to_string(); + + let schema = if let Some(qualifier) = path.qualifier() { + qualifier.syntax().text().to_string() + } else { + function_schema(create_function, binder)? + }; + + let param_list = create_function.param_list()?; + let params = param_list.syntax().text().to_string(); + + let ret_type = create_function.ret_type()?; + let return_type = ret_type.syntax().text().to_string(); + + Some(format!( + "function {}.{}{} {}", + schema, function_name, params, return_type + )) +} + +fn function_schema(create_function: &ast::CreateFunction, binder: &binder::Binder) -> Option { + let position = create_function.syntax().text_range().start(); + let search_path = binder.search_path_at(position); + search_path.first().map(|s| s.to_string()) +} + #[cfg(test)] mod test { use crate::hover::hover; @@ -718,4 +786,68 @@ drop index idx_x$0; ╰╴ ─ hover "); } + + #[test] + fn hover_on_drop_function() { + assert_snapshot!(check_hover(" +create function foo() returns int as $$ select 1 $$ language sql; +drop function foo$0(); +"), @r" + hover: function public.foo() returns int + ╭▸ + 3 │ drop function foo(); + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_drop_function_with_schema() { + assert_snapshot!(check_hover(" +create function myschema.foo() returns int as $$ select 1 $$ language sql; +drop function myschema.foo$0(); +"), @r" + hover: function myschema.foo() returns int + ╭▸ + 3 │ drop function myschema.foo(); + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_create_function_definition() { + assert_snapshot!(check_hover(" +create function foo$0() returns int as $$ select 1 $$ language sql; +"), @r" + hover: function public.foo() returns int + ╭▸ + 2 │ create function foo() returns int as $$ select 1 $$ language sql; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_create_function_with_explicit_schema() { + assert_snapshot!(check_hover(" +create function myschema.foo$0() returns int as $$ select 1 $$ language sql; +"), @r" + hover: function myschema.foo() returns int + ╭▸ + 2 │ create function myschema.foo() returns int as $$ select 1 $$ language sql; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_on_drop_function_with_search_path() { + assert_snapshot!(check_hover(r#" +set search_path to myschema; +create function foo() returns int as $$ select 1 $$ language sql; +drop function foo$0(); +"#), @r" + hover: function myschema.foo() returns int + ╭▸ + 4 │ drop function foo(); + ╰╴ ─ hover + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 4dbfabcb..046ceb79 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -84,26 +84,7 @@ fn resolve_table( schema: &Option, position: TextSize, ) -> Option { - let symbols = binder.scopes[binder.root_scope()].get(table_name)?; - - if let Some(schema) = schema { - let symbol_id = symbols.iter().copied().find(|id| { - let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Table && &symbol.schema == schema - })?; - return Some(binder.symbols[symbol_id].ptr); - } else { - let search_path = binder.search_path_at(position); - for search_schema in search_path { - if let Some(symbol_id) = symbols.iter().copied().find(|id| { - let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Table && &symbol.schema == search_schema - }) { - return Some(binder.symbols[symbol_id].ptr); - } - } - } - None + resolve_for_kind(binder, table_name, schema, position, SymbolKind::Table) } fn resolve_index( @@ -112,12 +93,22 @@ fn resolve_index( schema: &Option, position: TextSize, ) -> Option { - let symbols = binder.scopes[binder.root_scope()].get(index_name)?; + resolve_for_kind(binder, index_name, schema, position, SymbolKind::Index) +} + +fn resolve_for_kind( + binder: &Binder, + name: &Name, + schema: &Option, + position: TextSize, + kind: SymbolKind, +) -> Option { + let symbols = binder.scopes[binder.root_scope()].get(name)?; if let Some(schema) = schema { let symbol_id = symbols.iter().copied().find(|id| { let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Index && &symbol.schema == schema + symbol.kind == kind && &symbol.schema == schema })?; return Some(binder.symbols[symbol_id].ptr); } else { @@ -125,7 +116,7 @@ fn resolve_index( for search_schema in search_path { if let Some(symbol_id) = symbols.iter().copied().find(|id| { let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Index && &symbol.schema == search_schema + symbol.kind == kind && &symbol.schema == search_schema }) { return Some(binder.symbols[symbol_id].ptr); } @@ -140,26 +131,13 @@ fn resolve_function( schema: &Option, position: TextSize, ) -> Option { - let symbols = binder.scopes[binder.root_scope()].get(function_name)?; - - if let Some(schema) = schema { - let symbol_id = symbols.iter().copied().find(|id| { - let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Function && &symbol.schema == schema - })?; - return Some(binder.symbols[symbol_id].ptr); - } else { - let search_path = binder.search_path_at(position); - for search_schema in search_path { - if let Some(symbol_id) = symbols.iter().copied().find(|id| { - let symbol = &binder.symbols[*id]; - symbol.kind == SymbolKind::Function && &symbol.schema == search_schema - }) { - return Some(binder.symbols[symbol_id].ptr); - } - } - } - None + resolve_for_kind( + binder, + function_name, + schema, + position, + SymbolKind::Function, + ) } fn resolve_create_index_column(binder: &Binder, name_ref: &ast::NameRef) -> Option { From 870cfa90a738a6229a4108e1df0876d39deac9c6 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 27 Dec 2025 11:24:11 -0500 Subject: [PATCH 2/2] lint --- crates/squawk_ide/src/hover.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index a0b0050b..769e0f22 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -43,7 +43,10 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { return format_create_index(&create_index, &binder); } - if let Some(create_function) = name.syntax().ancestors().find_map(ast::CreateFunction::cast) + if let Some(create_function) = name + .syntax() + .ancestors() + .find_map(ast::CreateFunction::cast) { return format_create_function(&create_function, &binder); } @@ -340,7 +343,10 @@ fn format_create_function( )) } -fn function_schema(create_function: &ast::CreateFunction, binder: &binder::Binder) -> Option { +fn function_schema( + create_function: &ast::CreateFunction, + binder: &binder::Binder, +) -> Option { let position = create_function.syntax().text_range().start(); let search_path = binder.search_path_at(position); search_path.first().map(|s| s.to_string())