From ef83051ca954e2cb713064b6d696161b5d57b516 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sun, 11 Jan 2026 17:19:19 -0700 Subject: [PATCH] ide: goto def on extensions --- crates/squawk_ide/src/binder.rs | 20 ++++++++ crates/squawk_ide/src/classify.rs | 12 +++++ crates/squawk_ide/src/goto_definition.rs | 56 ++++++++++++++++++++++ crates/squawk_ide/src/hover.rs | 61 ++++++++++++++++++++++++ crates/squawk_ide/src/resolve.rs | 8 ++++ crates/squawk_ide/src/symbols.rs | 1 + 6 files changed, 158 insertions(+) diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index b658e048..d8d5a8f5 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -191,6 +191,7 @@ fn bind_stmt(b: &mut Binder, stmt: ast::Stmt) { } ast::Stmt::CreateDatabase(create_database) => bind_create_database(b, create_database), ast::Stmt::CreateServer(create_server) => bind_create_server(b, create_server), + ast::Stmt::CreateExtension(create_extension) => bind_create_extension(b, create_extension), ast::Stmt::Set(set) => bind_set(b, set), _ => {} } @@ -544,6 +545,25 @@ fn bind_create_server(b: &mut Binder, create_server: ast::CreateServer) { b.scopes[root].insert(server_name, server_id); } +fn bind_create_extension(b: &mut Binder, create_extension: ast::CreateExtension) { + let Some(name) = create_extension.name() else { + return; + }; + + let extension_name = Name::from_node(&name); + let name_ptr = SyntaxNodePtr::new(name.syntax()); + + let extension_id = b.symbols.alloc(Symbol { + kind: SymbolKind::Extension, + ptr: name_ptr, + schema: None, + params: None, + }); + + let root = b.root_scope(); + b.scopes[root].insert(extension_name, extension_id); +} + fn item_name(path: &ast::Path) -> Option { let segment = path.segment()?; diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index a2dde223..1ad6191f 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -15,6 +15,8 @@ pub(crate) enum NameRefClass { DropServer, AlterServer, CreateServer, + DropExtension, + AlterExtension, ForeignTableServerName, ForeignKeyTable, ForeignKeyColumn, @@ -389,6 +391,12 @@ pub(crate) fn classify_name_ref(name_ref: &ast::NameRef) -> Option if ast::CreateServer::can_cast(ancestor.kind()) { return Some(NameRefClass::CreateServer); } + if ast::DropExtension::can_cast(ancestor.kind()) { + return Some(NameRefClass::DropExtension); + } + if ast::AlterExtension::can_cast(ancestor.kind()) { + return Some(NameRefClass::AlterExtension); + } if ast::ServerName::can_cast(ancestor.kind()) { return Some(NameRefClass::ForeignTableServerName); } @@ -653,6 +661,7 @@ pub(crate) enum NameClass { CreateTablespace(ast::CreateTablespace), CreateDatabase(ast::CreateDatabase), CreateServer(ast::CreateServer), + CreateExtension(ast::CreateExtension), CreateType(ast::CreateType), CreateFunction(ast::CreateFunction), CreateAggregate(ast::CreateAggregate), @@ -699,6 +708,9 @@ pub(crate) fn classify_name(name: &ast::Name) -> Option { if let Some(create_server) = ast::CreateServer::cast(ancestor.clone()) { return Some(NameClass::CreateServer(create_server)); } + if let Some(create_extension) = ast::CreateExtension::cast(ancestor.clone()) { + return Some(NameClass::CreateExtension(create_extension)); + } if let Some(create_type) = ast::CreateType::cast(ancestor.clone()) { return Some(NameClass::CreateType(create_type)); } diff --git a/crates/squawk_ide/src/goto_definition.rs b/crates/squawk_ide/src/goto_definition.rs index a261ced6..5914cc52 100644 --- a/crates/squawk_ide/src/goto_definition.rs +++ b/crates/squawk_ide/src/goto_definition.rs @@ -472,6 +472,62 @@ create server my$0server foreign data wrapper fdw; "); } + #[test] + fn goto_drop_extension() { + assert_snapshot!(goto(" +create extension myext; +drop extension my$0ext; +"), @r" + ╭▸ + 2 │ create extension myext; + │ ───── 2. destination + 3 │ drop extension myext; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_drop_extension_defined_after() { + assert_snapshot!(goto(" +drop extension my$0ext; +create extension myext; +"), @r" + ╭▸ + 2 │ drop extension myext; + │ ─ 1. source + 3 │ create extension myext; + ╰╴ ───── 2. destination + "); + } + + #[test] + fn goto_alter_extension() { + assert_snapshot!(goto(" +create extension myext; +alter extension my$0ext update to '2.0'; +"), @r" + ╭▸ + 2 │ create extension myext; + │ ───── 2. destination + 3 │ alter extension myext update to '2.0'; + ╰╴ ─ 1. source + "); + } + + #[test] + fn goto_extension_definition_returns_self() { + assert_snapshot!(goto(" +create extension my$0ext; +"), @r" + ╭▸ + 2 │ create extension myext; + │ ┬┬─── + │ ││ + │ │1. source + ╰╴ 2. destination + "); + } + #[test] fn goto_drop_sequence_with_schema() { assert_snapshot!(goto(" diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 943e7291..27ba878e 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -131,6 +131,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { | NameRefClass::ForeignTableServerName => { return hover_server(root, &name_ref, &binder); } + NameRefClass::DropExtension | NameRefClass::AlterExtension => { + return hover_extension(root, &name_ref, &binder); + } NameRefClass::Tablespace => return hover_tablespace(root, &name_ref, &binder), NameRefClass::DropIndex | NameRefClass::ReindexIndex => { return hover_index(root, &name_ref, &binder); @@ -186,6 +189,9 @@ pub fn hover(file: &ast::SourceFile, offset: TextSize) -> Option { NameClass::CreateServer(create_server) => { return format_create_server(&create_server); } + NameClass::CreateExtension(create_extension) => { + return format_create_extension(&create_extension); + } NameClass::CreateType(create_type) => { return format_create_type(&create_type, &binder); } @@ -751,6 +757,18 @@ fn hover_server( Some(format!("server {}", server_name_node.text())) } +fn hover_extension( + root: &SyntaxNode, + name_ref: &ast::NameRef, + binder: &binder::Binder, +) -> Option { + let extension_ptr = resolve::resolve_name_ref(binder, root, name_ref)? + .into_iter() + .next()?; + let extension_name_node = extension_ptr.to_node(root); + Some(format!("extension {}", extension_name_node.text())) +} + fn hover_type( root: &SyntaxNode, name_ref: &ast::NameRef, @@ -891,6 +909,11 @@ fn format_create_server(create_server: &ast::CreateServer) -> Option { Some(format!("server {}", name)) } +fn format_create_extension(create_extension: &ast::CreateExtension) -> Option { + let name = create_extension.name()?.syntax().text().to_string(); + Some(format!("extension {}", name)) +} + fn index_schema(create_index: &ast::CreateIndex, binder: &binder::Binder) -> Option { let position = create_index.syntax().text_range().start(); let search_path = binder.search_path_at(position); @@ -3773,4 +3796,42 @@ select a$0 from u; ╰╴ ─ hover "); } + + #[test] + fn hover_extension_on_create() { + assert_snapshot!(check_hover(" +create extension my$0ext; +"), @r" + hover: extension myext + ╭▸ + 2 │ create extension myext; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_extension_on_drop() { + assert_snapshot!(check_hover(" +create extension myext; +drop extension my$0ext; +"), @r" + hover: extension myext + ╭▸ + 3 │ drop extension myext; + ╰╴ ─ hover + "); + } + + #[test] + fn hover_extension_on_alter() { + assert_snapshot!(check_hover(" +create extension myext; +alter extension my$0ext update to '2.0'; +"), @r" + hover: extension myext + ╭▸ + 3 │ alter extension myext update to '2.0'; + ╰╴ ─ hover + "); + } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index c6b0372e..6d5013cc 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -133,6 +133,10 @@ pub(crate) fn resolve_name_ref( let server_name = Name::from_node(name_ref); resolve_server_name_ptr(binder, &server_name).map(|ptr| smallvec![ptr]) } + NameRefClass::DropExtension | NameRefClass::AlterExtension => { + let extension_name = Name::from_node(name_ref); + resolve_extension_name_ptr(binder, &extension_name).map(|ptr| smallvec![ptr]) + } NameRefClass::SequenceOwnedByColumn => { let sequence_option = name_ref .syntax() @@ -472,6 +476,10 @@ fn resolve_server_name_ptr(binder: &Binder, server_name: &Name) -> Option Option { + binder.lookup(extension_name, SymbolKind::Extension) +} + fn resolve_for_kind_with_params( binder: &Binder, name: &Name, diff --git a/crates/squawk_ide/src/symbols.rs b/crates/squawk_ide/src/symbols.rs index 78fba0e9..6c9e83fc 100644 --- a/crates/squawk_ide/src/symbols.rs +++ b/crates/squawk_ide/src/symbols.rs @@ -56,6 +56,7 @@ pub(crate) enum SymbolKind { Tablespace, Database, Server, + Extension, } #[derive(Clone, Debug)]