Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion crates/squawk_ide/src/code_actions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rowan::TextSize;
use rowan::{TextRange, TextSize};
use squawk_linter::Edit;
use squawk_syntax::{
SyntaxKind,
Expand All @@ -9,6 +9,7 @@ use crate::{
column_name::ColumnName,
offsets::token_from_offset,
quote::{quote_column_alias, unquote_ident},
symbols::Name,
};

#[derive(Debug, Clone)]
Expand All @@ -34,6 +35,7 @@ pub fn code_actions(file: ast::SourceFile, offset: TextSize) -> Option<Vec<CodeA
quote_identifier(&mut actions, &file, offset);
unquote_identifier(&mut actions, &file, offset);
add_explicit_alias(&mut actions, &file, offset);
remove_redundant_alias(&mut actions, &file, offset);
Some(actions)
}

Expand Down Expand Up @@ -395,6 +397,45 @@ fn add_explicit_alias(
Some(())
}

fn remove_redundant_alias(
actions: &mut Vec<CodeAction>,
file: &ast::SourceFile,
offset: TextSize,
) -> Option<()> {
let token = token_from_offset(file, offset)?;
let target = token.parent_ancestors().find_map(ast::Target::cast)?;

let as_name = target.as_name()?;
let alias_name = as_name.name()?;

let (inferred_column, _) = ColumnName::inferred_from_target(target.clone())?;
let inferred_column_alias = inferred_column.to_string()?;

let alias = alias_name.syntax().text().to_string();

if Name::new(alias) != Name::new(inferred_column_alias) {
return None;
}

// TODO:
// This lets use remove any whitespace so we don't end up with:
// select x as x, b from t;
// becoming
// select x , b from t;
// but we probably want a better way to express this.
// Maybe a "Remove preceding whitespace" style option for edits.
let expr_end = target.expr()?.syntax().text_range().end();
let alias_end = as_name.syntax().text_range().end();

actions.push(CodeAction {
title: "Remove redundant alias".to_owned(),
edits: vec![Edit::delete(TextRange::new(expr_end, alias_end))],
kind: ActionKind::QuickFix,
});

Some(())
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -1045,4 +1086,65 @@ mod test {
@r#"select 'foo' as "?column?" from t;"#
);
}

#[test]
fn remove_redundant_alias_simple() {
assert_snapshot!(apply_code_action(
remove_redundant_alias,
"select col_name as col_na$0me from t;"),
@"select col_name from t;"
);
}

#[test]
fn remove_redundant_alias_quoted() {
assert_snapshot!(apply_code_action(
remove_redundant_alias,
r#"select "x"$0 as x from t;"#),
@r#"select "x" from t;"#
);
}

#[test]
fn remove_redundant_alias_case_insensitive() {
assert_snapshot!(apply_code_action(
remove_redundant_alias,
"select col_name$0 as COL_NAME from t;"),
@"select col_name from t;"
);
}

#[test]
fn remove_redundant_alias_function() {
assert_snapshot!(apply_code_action(
remove_redundant_alias,
"select count(*)$0 as count from t;"),
@"select count(*) from t;"
);
}

#[test]
fn remove_redundant_alias_field_expr() {
assert_snapshot!(apply_code_action(
remove_redundant_alias,
"select t.col$0umn as column from t;"),
@"select t.column from t;"
);
}

#[test]
fn remove_redundant_alias_not_applicable_different_name() {
assert!(code_action_not_applicable(
remove_redundant_alias,
"select col_name$0 as foo from t;"
));
}

#[test]
fn remove_redundant_alias_not_applicable_no_alias() {
assert!(code_action_not_applicable(
remove_redundant_alias,
"select col_name$0 from t;"
));
}
}
17 changes: 9 additions & 8 deletions crates/squawk_ide/src/column_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ use squawk_syntax::{
ast::{self, AstNode},
};

fn normalize_identifier(text: &str) -> String {
if text.starts_with('"') && text.ends_with('"') {
text[1..text.len() - 1].to_string()
} else {
text.to_lowercase()
}
}
use crate::quote::normalize_identifier;

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ColumnName {
Expand All @@ -30,14 +24,21 @@ pub(crate) enum ColumnName {
}

impl ColumnName {
// Get the alias, otherwise infer the column name.
pub(crate) fn from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
if let Some(as_name) = target.as_name()
&& let Some(name_node) = as_name.name()
{
let text = name_node.text();
let normalized = normalize_identifier(&text);
return Some((ColumnName::Column(normalized), name_node.syntax().clone()));
} else if let Some(expr) = target.expr()
}
Self::inferred_from_target(target)
}

// Ignore any aliases, just infer the what the column name.
pub(crate) fn inferred_from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
if let Some(expr) = target.expr()
&& let Some(name) = name_from_expr(expr, false)
{
return Some(name);
Expand Down
8 changes: 8 additions & 0 deletions crates/squawk_ide/src/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ pub(crate) fn is_reserved_word(text: &str) -> bool {
.is_ok()
}

pub(crate) fn normalize_identifier(text: &str) -> String {
if text.starts_with('"') && text.ends_with('"') && text.len() >= 2 {
text[1..text.len() - 1].to_string()
} else {
text.to_lowercase()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
26 changes: 17 additions & 9 deletions crates/squawk_ide/src/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use smol_str::SmolStr;
use squawk_syntax::SyntaxNodePtr;
use std::fmt;

use crate::quote::normalize_identifier;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct Name(pub(crate) SmolStr);

Expand All @@ -25,7 +27,7 @@ impl Name {
pub(crate) fn new(text: impl Into<SmolStr>) -> Self {
let text = text.into();
let normalized = normalize_identifier(&text);
Name(normalized)
Name(normalized.into())
}
}

Expand All @@ -35,14 +37,6 @@ impl fmt::Display for Name {
}
}

fn normalize_identifier(text: &str) -> SmolStr {
if text.starts_with('"') && text.ends_with('"') && text.len() >= 2 {
text[1..text.len() - 1].into()
} else {
text.to_lowercase().into()
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SymbolKind {
Table,
Expand All @@ -59,3 +53,17 @@ pub(crate) struct Symbol {
}

pub(crate) type SymbolId = Idx<Symbol>;

#[cfg(test)]
mod test {
use super::*;
#[test]
fn name_case_insensitive_compare() {
assert_eq!(Name::new("foo"), Name::new("FOO"));
}

#[test]
fn name_quote_comparing() {
assert_eq!(Name::new(r#""foo""#), Name::new("foo"));
}
}
Loading