use rowan::TextSize;
use salsa::Database as Db;
use squawk_linter::Edit;
use squawk_syntax::ast::{self, AstNode};
use super::{ActionKind, CodeAction};
use crate::{db::bind, file::InFile, offsets::token_from_offset};
pub(super) fn add_schema(
db: &dyn Db,
position: InFile<TextSize>,
actions: &mut Vec<CodeAction>,
) -> Option<()> {
let file = position.file_id;
let offset = position.value;
let token = token_from_offset(db, position)?;
let range = token.parent_ancestors().find_map(|node| {
if let Some(path) = ast::Path::cast(node.clone()) {
if path.qualifier().is_some() {
return None;
}
return Some(path.syntax().text_range());
}
if let Some(from_item) = ast::FromItem::cast(node.clone()) {
let name_ref = from_item.name_ref()?;
return Some(name_ref.syntax().text_range());
}
if let Some(call_expr) = ast::CallExpr::cast(node) {
let ast::Expr::NameRef(name_ref) = call_expr.expr()? else {
return None;
};
return Some(name_ref.syntax().text_range());
}
None
})?;
if !range.contains(offset) {
return None;
}
let token_start = token.text_range().start();
let schema = bind(db, file)
.search_path_at(token_start)
.first()?
.to_string();
let replacement = format!("{schema}.");
actions.push(CodeAction {
title: "Add schema".to_owned(),
edits: vec![Edit::insert(replacement, token_start)],
kind: ActionKind::RefactorRewrite,
});
Some(())
}
#[cfg(test)]
mod test {
use insta::assert_snapshot;
use crate::code_actions::test_utils::{apply_code_action, code_action_not_applicable};
use super::add_schema;
#[test]
fn add_schema_simple() {
assert_snapshot!(apply_code_action(
add_schema,
"create table t$0(a text, b int);"),
@"create table public.t(a text, b int);"
);
}
#[test]
fn add_schema_create_foreign_table() {
assert_snapshot!(apply_code_action(
add_schema,
"create foreign table t$0(a text, b int) server foo;"),
@"create foreign table public.t(a text, b int) server foo;"
);
}
#[test]
fn add_schema_create_function() {
assert_snapshot!(apply_code_action(
add_schema,
"create function f$0() returns int8\n as 'select 1'\n language sql;"),
@"create function public.f() returns int8
as 'select 1'
language sql;"
);
}
#[test]
fn add_schema_create_type() {
assert_snapshot!(apply_code_action(
add_schema,
"create type t$0 as enum ();"),
@"create type public.t as enum ();"
);
}
#[test]
fn add_schema_table_stmt() {
assert_snapshot!(apply_code_action(
add_schema,
"table t$0;"),
@"table public.t;"
);
}
#[test]
fn add_schema_select_from() {
assert_snapshot!(apply_code_action(
add_schema,
"create table t(a text, b int);
select t from t$0;"),
@"create table t(a text, b int);
select t from public.t;"
);
}
#[test]
fn add_schema_select_table_value() {
assert!(code_action_not_applicable(
add_schema,
"create table t(a text, b int);
select t$0 from t;"
));
}
#[test]
fn add_schema_select_unqualified_column() {
assert!(code_action_not_applicable(
add_schema,
"create table t(a text, b int);
select a$0 from t;"
));
}
#[test]
fn add_schema_select_qualified_column() {
assert!(code_action_not_applicable(
add_schema,
"create table t(c text);
select t$0.c from t;"
));
}
#[test]
fn add_schema_with_search_path() {
assert_snapshot!(
apply_code_action(
add_schema,
"
set search_path to myschema;
create table t$0(a text, b int);"
),
@"
set search_path to myschema;
create table myschema.t(a text, b int);"
);
}
#[test]
fn add_schema_not_applicable_with_schema() {
assert!(code_action_not_applicable(
add_schema,
"create table myschema.t$0(a text, b int);"
));
}
#[test]
fn add_schema_function_call() {
assert_snapshot!(apply_code_action(
add_schema,
"
create function f() returns int8
as 'select 1'
language sql;
select f$0();"),
@"
create function f() returns int8
as 'select 1'
language sql;
select public.f();"
);
}
#[test]
fn add_schema_function_call_not_applicable_with_schema() {
assert!(code_action_not_applicable(
add_schema,
"
create function f() returns int8 as 'select 1' language sql;
select myschema.f$0();"
));
}
}