From 769f3c0a3fca39306e00f68ffc705dcdb9c00ec8 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Wed, 15 Apr 2026 09:14:35 -0400 Subject: [PATCH] ide: code action to rewrite between != and <> not really necessary and something that the formatter will handle, but doing it because we can --- crates/squawk_ide/src/code_actions.rs | 56 ++++++++++++++++++++++++ crates/squawk_syntax/src/ast/node_ext.rs | 24 +++++----- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/crates/squawk_ide/src/code_actions.rs b/crates/squawk_ide/src/code_actions.rs index 1f0965ee..5d67d6ef 100644 --- a/crates/squawk_ide/src/code_actions.rs +++ b/crates/squawk_ide/src/code_actions.rs @@ -53,6 +53,7 @@ pub fn code_actions(db: &dyn Db, file: File, offset: TextSize) -> Option, + file: &ast::SourceFile, + offset: TextSize, +) -> Option<()> { + let token = token_from_offset(file, offset)?; + let bin_expr = token.parent_ancestors().find_map(ast::BinExpr::cast)?; + + let (op_token, replacement, title) = match bin_expr.op()? { + ast::BinOp::Neq(token) => (token, "<>", "Rewrite `!=` as `<>`"), + ast::BinOp::Neqb(token) => (token, "!=", "Rewrite `<>` as `!=`"), + _ => return None, + }; + + actions.push(CodeAction { + title: title.to_owned(), + edits: vec![Edit::replace(op_token.text_range(), replacement.to_owned())], + kind: ActionKind::RefactorRewrite, + }); + + Some(()) +} + fn rewrite_timestamp_type( actions: &mut Vec, file: &ast::SourceFile, @@ -2003,6 +2027,38 @@ select myschema.f$0();" )); } + #[test] + fn rewrite_not_equals_bang_to_angle() { + assert_snapshot!( + apply_code_action(rewrite_not_equals_operator, "select 1 !$0= 2;"), + @"select 1 <> 2;" + ); + } + + #[test] + fn rewrite_not_equals_angle_to_bang() { + assert_snapshot!( + apply_code_action(rewrite_not_equals_operator, "select 1 <$0> 2;"), + @"select 1 != 2;" + ); + } + + #[test] + fn rewrite_not_equals_cursor_on_operand() { + assert_snapshot!( + apply_code_action(rewrite_not_equals_operator, "select a$0 != b from t;"), + @"select a <> b from t;" + ); + } + + #[test] + fn rewrite_not_equals_not_applicable_other_op() { + assert!(code_action_not_applicable( + rewrite_not_equals_operator, + "select 1 =$0 2;" + )); + } + #[test] fn rewrite_values_as_select_simple() { assert_snapshot!( diff --git a/crates/squawk_syntax/src/ast/node_ext.rs b/crates/squawk_syntax/src/ast/node_ext.rs index 4c58ee9b..2c3adab3 100644 --- a/crates/squawk_syntax/src/ast/node_ext.rs +++ b/crates/squawk_syntax/src/ast/node_ext.rs @@ -88,11 +88,11 @@ pub enum BinOp { Caret(SyntaxToken), Collate(SyntaxToken), ColonColon(ast::ColonColon), - ColonEq(ast::ColonEq), + ColonEq(SyntaxToken), CustomOp(ast::CustomOp), Eq(SyntaxToken), - FatArrow(ast::FatArrow), - Gteq(ast::Gteq), + FatArrow(SyntaxToken), + Gteq(SyntaxToken), Ilike(SyntaxToken), In(SyntaxToken), Is(SyntaxToken), @@ -101,10 +101,10 @@ pub enum BinOp { IsNotDistinctFrom(ast::IsNotDistinctFrom), LAngle(SyntaxToken), Like(SyntaxToken), - Lteq(ast::Lteq), + Lteq(SyntaxToken), Minus(SyntaxToken), - Neq(ast::Neq), - Neqb(ast::Neqb), + Neq(SyntaxToken), + Neqb(SyntaxToken), NotIlike(ast::NotIlike), NotIn(ast::NotIn), NotLike(ast::NotLike), @@ -159,13 +159,19 @@ impl ast::BinExpr { SyntaxKind::AND_KW => BinOp::And(token), SyntaxKind::CARET => BinOp::Caret(token), SyntaxKind::COLLATE_KW => BinOp::Collate(token), + SyntaxKind::COLON_EQ => BinOp::ColonEq(token), SyntaxKind::EQ => BinOp::Eq(token), + SyntaxKind::FAT_ARROW => BinOp::FatArrow(token), + SyntaxKind::GTEQ => BinOp::Gteq(token), SyntaxKind::ILIKE_KW => BinOp::Ilike(token), SyntaxKind::IN_KW => BinOp::In(token), SyntaxKind::IS_KW => BinOp::Is(token), SyntaxKind::L_ANGLE => BinOp::LAngle(token), SyntaxKind::LIKE_KW => BinOp::Like(token), + SyntaxKind::LTEQ => BinOp::Lteq(token), SyntaxKind::MINUS => BinOp::Minus(token), + SyntaxKind::NEQ => BinOp::Neq(token), + SyntaxKind::NEQB => BinOp::Neqb(token), SyntaxKind::OR_KW => BinOp::Or(token), SyntaxKind::OVERLAPS_KW => BinOp::Overlaps(token), SyntaxKind::PERCENT => BinOp::Percent(token), @@ -185,10 +191,7 @@ impl ast::BinExpr { SyntaxKind::COLON_COLON => { BinOp::ColonColon(ast::ColonColon { syntax: node }) } - SyntaxKind::COLON_EQ => BinOp::ColonEq(ast::ColonEq { syntax: node }), SyntaxKind::CUSTOM_OP => BinOp::CustomOp(ast::CustomOp { syntax: node }), - SyntaxKind::FAT_ARROW => BinOp::FatArrow(ast::FatArrow { syntax: node }), - SyntaxKind::GTEQ => BinOp::Gteq(ast::Gteq { syntax: node }), SyntaxKind::IS_DISTINCT_FROM => { BinOp::IsDistinctFrom(ast::IsDistinctFrom { syntax: node }) } @@ -196,9 +199,6 @@ impl ast::BinExpr { SyntaxKind::IS_NOT_DISTINCT_FROM => { BinOp::IsNotDistinctFrom(ast::IsNotDistinctFrom { syntax: node }) } - SyntaxKind::LTEQ => BinOp::Lteq(ast::Lteq { syntax: node }), - SyntaxKind::NEQ => BinOp::Neq(ast::Neq { syntax: node }), - SyntaxKind::NEQB => BinOp::Neqb(ast::Neqb { syntax: node }), SyntaxKind::NOT_ILIKE => BinOp::NotIlike(ast::NotIlike { syntax: node }), SyntaxKind::NOT_IN => BinOp::NotIn(ast::NotIn { syntax: node }), SyntaxKind::NOT_LIKE => BinOp::NotLike(ast::NotLike { syntax: node }),