From e9ea9d802755a3b1181d6bfe74c404b9718dfa54 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Mon, 31 Aug 2026 09:53:44 -0400 Subject: [PATCH] ide: code actions for func params --- crates/squawk_ide/src/code_actions/mod.rs | 6 + .../code_actions/remove_function_param_in.rs | 107 ++++++++++++++++++ .../rewrite_function_param_in_out_as_inout.rs | 84 ++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 crates/squawk_ide/src/code_actions/remove_function_param_in.rs create mode 100644 crates/squawk_ide/src/code_actions/rewrite_function_param_in_out_as_inout.rs diff --git a/crates/squawk_ide/src/code_actions/mod.rs b/crates/squawk_ide/src/code_actions/mod.rs index 0c9dfb4b..667fa5c0 100644 --- a/crates/squawk_ide/src/code_actions/mod.rs +++ b/crates/squawk_ide/src/code_actions/mod.rs @@ -9,6 +9,7 @@ mod add_schema; mod convert_comment; mod quote_identifier; mod remove_else_clause; +mod remove_function_param_in; mod remove_redundant_alias; mod rewrite_as_dollar_quoted_string; mod rewrite_as_regular_string; @@ -23,6 +24,7 @@ mod rewrite_double_colon_to_cast; mod rewrite_extract_as_function_call; mod rewrite_from; mod rewrite_function_param_default_as_equals; +mod rewrite_function_param_in_out_as_inout; mod rewrite_in_as_expression; mod rewrite_integer_radix; mod rewrite_is_normalized_as_function_call; @@ -58,6 +60,7 @@ use add_schema::add_schema; use convert_comment::convert_comment; use quote_identifier::quote_identifier; use remove_else_clause::remove_else_clause; +use remove_function_param_in::remove_function_param_in; use remove_redundant_alias::remove_redundant_alias; use rewrite_as_dollar_quoted_string::rewrite_as_dollar_quoted_string; use rewrite_as_regular_string::rewrite_as_regular_string; @@ -72,6 +75,7 @@ use rewrite_double_colon_to_cast::rewrite_double_colon_to_cast; use rewrite_extract_as_function_call::rewrite_extract_as_function_call; use rewrite_from::rewrite_from; use rewrite_function_param_default_as_equals::rewrite_function_param_default_as_equals; +use rewrite_function_param_in_out_as_inout::rewrite_function_param_in_out_as_inout; use rewrite_in_as_expression::rewrite_in_as_expression; use rewrite_integer_radix::rewrite_integer_radix; use rewrite_is_normalized_as_function_call::rewrite_is_normalized_as_function_call; @@ -121,6 +125,8 @@ pub fn code_actions(db: &dyn Db, position: InFile) -> Option, + actions: &mut Vec, +) -> Option<()> { + let token = token_from_offset(db, position)?; + let param = token.parent_ancestors().find_map(ast::Param::cast)?; + param + .syntax() + .ancestors() + .find_map(ast::CreateFunction::cast)?; + let ast::ParamMode::ParamIn(mode) = param.mode()? else { + return None; + }; + let in_token = mode.in_token()?; + let delete_range = in_token + .next_token() + .filter(|token| token.kind() == SyntaxKind::WHITESPACE) + .map(|token| TextRange::new(in_token.text_range().start(), token.text_range().end())) + .unwrap_or_else(|| in_token.text_range()); + + actions.push(CodeAction { + title: "Remove redundant `IN`".to_owned(), + edits: vec![Edit::delete(delete_range)], + 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::remove_function_param_in; + + #[test] + fn removes_in() { + assert_snapshot!( + apply_code_action( + remove_function_param_in, + "create function f(i$0n value int) returns int language sql as $$ select value $$;", + ), + @"create function f(value int) returns int language sql as $$ select value $$;" + ); + } + + #[test] + fn preserves_comments() { + assert_snapshot!( + apply_code_action( + remove_function_param_in, + "create function f(i$0n /* before value */ value int) returns int language sql as $$ select value $$;", + ), + @"create function f(/* before value */ value int) returns int language sql as $$ select value $$;" + ); + } + + #[test] + fn applies_when_mode_follows_name() { + assert_snapshot!( + apply_code_action( + remove_function_param_in, + "create function f(value i$0n int) returns int language sql as $$ select value $$;", + ), + @"create function f(value int) returns int language sql as $$ select value $$;" + ); + } + + #[test] + fn not_applicable_to_out() { + assert!(code_action_not_applicable( + remove_function_param_in, + "create function f(o$0ut value int) returns int language sql as $$ select value $$;" + )); + } + + #[test] + fn not_applicable_to_inout() { + assert!(code_action_not_applicable( + remove_function_param_in, + "create function f(ino$0ut value int) returns int language sql as $$ select value $$;" + )); + } + + #[test] + fn not_applicable_to_procedure_param() { + assert!(code_action_not_applicable( + remove_function_param_in, + "create procedure p(i$0n value int) language sql as $$ select value $$;" + )); + } +} diff --git a/crates/squawk_ide/src/code_actions/rewrite_function_param_in_out_as_inout.rs b/crates/squawk_ide/src/code_actions/rewrite_function_param_in_out_as_inout.rs new file mode 100644 index 00000000..e8de4d17 --- /dev/null +++ b/crates/squawk_ide/src/code_actions/rewrite_function_param_in_out_as_inout.rs @@ -0,0 +1,84 @@ +use rowan::TextSize; +use salsa::Database as Db; +use squawk_linter::Edit; +use squawk_syntax::ast::{self, AstNode}; + +use crate::{file::InFile, offsets::token_from_offset}; + +use super::{ActionKind, CodeAction}; + +pub(super) fn rewrite_function_param_in_out_as_inout( + db: &dyn Db, + position: InFile, + actions: &mut Vec, +) -> Option<()> { + let token = token_from_offset(db, position)?; + let param = token.parent_ancestors().find_map(ast::Param::cast)?; + param + .syntax() + .ancestors() + .find_map(ast::CreateFunction::cast)?; + let ast::ParamMode::ParamInOut(mode) = param.mode()? else { + return None; + }; + mode.in_token()?; + mode.out_token()?; + + actions.push(CodeAction { + title: "Rewrite `IN OUT` as `INOUT`".to_owned(), + edits: vec![Edit::replace( + mode.syntax().text_range(), + "inout".to_owned(), + )], + 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::rewrite_function_param_in_out_as_inout; + + #[test] + fn rewrites_in_out_as_inout() { + assert_snapshot!( + apply_code_action( + rewrite_function_param_in_out_as_inout, + "create function f(in $0out value int) returns int language sql as $$ select value $$;", + ), + @"create function f(inout value int) returns int language sql as $$ select value $$;" + ); + } + + #[test] + fn applies_when_mode_follows_name() { + assert_snapshot!( + apply_code_action( + rewrite_function_param_in_out_as_inout, + "create function f(value in o$0ut int) returns int language sql as $$ select value $$;", + ), + @"create function f(value inout int) returns int language sql as $$ select value $$;" + ); + } + + #[test] + fn not_applicable_to_inout() { + assert!(code_action_not_applicable( + rewrite_function_param_in_out_as_inout, + "create function f(ino$0ut value int) returns int language sql as $$ select value $$;" + )); + } + + #[test] + fn not_applicable_to_procedure_param() { + assert!(code_action_not_applicable( + rewrite_function_param_in_out_as_inout, + "create procedure p(in o$0ut value int) language sql as $$ select value $$;" + )); + } +}