Skip to content

Commit 01c790f

Browse files
authored
ide: remove redundant alias (#785)
1 parent 553a2b0 commit 01c790f

File tree

4 files changed

+137
-18
lines changed

4 files changed

+137
-18
lines changed

crates/squawk_ide/src/code_actions.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rowan::TextSize;
1+
use rowan::{TextRange, TextSize};
22
use squawk_linter::Edit;
33
use squawk_syntax::{
44
SyntaxKind,
@@ -9,6 +9,7 @@ use crate::{
99
column_name::ColumnName,
1010
offsets::token_from_offset,
1111
quote::{quote_column_alias, unquote_ident},
12+
symbols::Name,
1213
};
1314

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

@@ -395,6 +397,45 @@ fn add_explicit_alias(
395397
Some(())
396398
}
397399

400+
fn remove_redundant_alias(
401+
actions: &mut Vec<CodeAction>,
402+
file: &ast::SourceFile,
403+
offset: TextSize,
404+
) -> Option<()> {
405+
let token = token_from_offset(file, offset)?;
406+
let target = token.parent_ancestors().find_map(ast::Target::cast)?;
407+
408+
let as_name = target.as_name()?;
409+
let alias_name = as_name.name()?;
410+
411+
let (inferred_column, _) = ColumnName::inferred_from_target(target.clone())?;
412+
let inferred_column_alias = inferred_column.to_string()?;
413+
414+
let alias = alias_name.syntax().text().to_string();
415+
416+
if Name::new(alias) != Name::new(inferred_column_alias) {
417+
return None;
418+
}
419+
420+
// TODO:
421+
// This lets use remove any whitespace so we don't end up with:
422+
// select x as x, b from t;
423+
// becoming
424+
// select x , b from t;
425+
// but we probably want a better way to express this.
426+
// Maybe a "Remove preceding whitespace" style option for edits.
427+
let expr_end = target.expr()?.syntax().text_range().end();
428+
let alias_end = as_name.syntax().text_range().end();
429+
430+
actions.push(CodeAction {
431+
title: "Remove redundant alias".to_owned(),
432+
edits: vec![Edit::delete(TextRange::new(expr_end, alias_end))],
433+
kind: ActionKind::QuickFix,
434+
});
435+
436+
Some(())
437+
}
438+
398439
#[cfg(test)]
399440
mod test {
400441
use super::*;
@@ -1045,4 +1086,65 @@ mod test {
10451086
@r#"select 'foo' as "?column?" from t;"#
10461087
);
10471088
}
1089+
1090+
#[test]
1091+
fn remove_redundant_alias_simple() {
1092+
assert_snapshot!(apply_code_action(
1093+
remove_redundant_alias,
1094+
"select col_name as col_na$0me from t;"),
1095+
@"select col_name from t;"
1096+
);
1097+
}
1098+
1099+
#[test]
1100+
fn remove_redundant_alias_quoted() {
1101+
assert_snapshot!(apply_code_action(
1102+
remove_redundant_alias,
1103+
r#"select "x"$0 as x from t;"#),
1104+
@r#"select "x" from t;"#
1105+
);
1106+
}
1107+
1108+
#[test]
1109+
fn remove_redundant_alias_case_insensitive() {
1110+
assert_snapshot!(apply_code_action(
1111+
remove_redundant_alias,
1112+
"select col_name$0 as COL_NAME from t;"),
1113+
@"select col_name from t;"
1114+
);
1115+
}
1116+
1117+
#[test]
1118+
fn remove_redundant_alias_function() {
1119+
assert_snapshot!(apply_code_action(
1120+
remove_redundant_alias,
1121+
"select count(*)$0 as count from t;"),
1122+
@"select count(*) from t;"
1123+
);
1124+
}
1125+
1126+
#[test]
1127+
fn remove_redundant_alias_field_expr() {
1128+
assert_snapshot!(apply_code_action(
1129+
remove_redundant_alias,
1130+
"select t.col$0umn as column from t;"),
1131+
@"select t.column from t;"
1132+
);
1133+
}
1134+
1135+
#[test]
1136+
fn remove_redundant_alias_not_applicable_different_name() {
1137+
assert!(code_action_not_applicable(
1138+
remove_redundant_alias,
1139+
"select col_name$0 as foo from t;"
1140+
));
1141+
}
1142+
1143+
#[test]
1144+
fn remove_redundant_alias_not_applicable_no_alias() {
1145+
assert!(code_action_not_applicable(
1146+
remove_redundant_alias,
1147+
"select col_name$0 from t;"
1148+
));
1149+
}
10481150
}

crates/squawk_ide/src/column_name.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@ use squawk_syntax::{
33
ast::{self, AstNode},
44
};
55

6-
fn normalize_identifier(text: &str) -> String {
7-
if text.starts_with('"') && text.ends_with('"') {
8-
text[1..text.len() - 1].to_string()
9-
} else {
10-
text.to_lowercase()
11-
}
12-
}
6+
use crate::quote::normalize_identifier;
137

148
#[derive(Clone, Debug, PartialEq)]
159
pub(crate) enum ColumnName {
@@ -30,14 +24,21 @@ pub(crate) enum ColumnName {
3024
}
3125

3226
impl ColumnName {
27+
// Get the alias, otherwise infer the column name.
3328
pub(crate) fn from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
3429
if let Some(as_name) = target.as_name()
3530
&& let Some(name_node) = as_name.name()
3631
{
3732
let text = name_node.text();
3833
let normalized = normalize_identifier(&text);
3934
return Some((ColumnName::Column(normalized), name_node.syntax().clone()));
40-
} else if let Some(expr) = target.expr()
35+
}
36+
Self::inferred_from_target(target)
37+
}
38+
39+
// Ignore any aliases, just infer the what the column name.
40+
pub(crate) fn inferred_from_target(target: ast::Target) -> Option<(ColumnName, SyntaxNode)> {
41+
if let Some(expr) = target.expr()
4142
&& let Some(name) = name_from_expr(expr, false)
4243
{
4344
return Some(name);

crates/squawk_ide/src/quote.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ pub(crate) fn is_reserved_word(text: &str) -> bool {
7777
.is_ok()
7878
}
7979

80+
pub(crate) fn normalize_identifier(text: &str) -> String {
81+
if text.starts_with('"') && text.ends_with('"') && text.len() >= 2 {
82+
text[1..text.len() - 1].to_string()
83+
} else {
84+
text.to_lowercase()
85+
}
86+
}
87+
8088
#[cfg(test)]
8189
mod tests {
8290
use super::*;

crates/squawk_ide/src/symbols.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use smol_str::SmolStr;
33
use squawk_syntax::SyntaxNodePtr;
44
use std::fmt;
55

6+
use crate::quote::normalize_identifier;
7+
68
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
79
pub(crate) struct Name(pub(crate) SmolStr);
810

@@ -25,7 +27,7 @@ impl Name {
2527
pub(crate) fn new(text: impl Into<SmolStr>) -> Self {
2628
let text = text.into();
2729
let normalized = normalize_identifier(&text);
28-
Name(normalized)
30+
Name(normalized.into())
2931
}
3032
}
3133

@@ -35,14 +37,6 @@ impl fmt::Display for Name {
3537
}
3638
}
3739

38-
fn normalize_identifier(text: &str) -> SmolStr {
39-
if text.starts_with('"') && text.ends_with('"') && text.len() >= 2 {
40-
text[1..text.len() - 1].into()
41-
} else {
42-
text.to_lowercase().into()
43-
}
44-
}
45-
4640
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
4741
pub(crate) enum SymbolKind {
4842
Table,
@@ -59,3 +53,17 @@ pub(crate) struct Symbol {
5953
}
6054

6155
pub(crate) type SymbolId = Idx<Symbol>;
56+
57+
#[cfg(test)]
58+
mod test {
59+
use super::*;
60+
#[test]
61+
fn name_case_insensitive_compare() {
62+
assert_eq!(Name::new("foo"), Name::new("FOO"));
63+
}
64+
65+
#[test]
66+
fn name_quote_comparing() {
67+
assert_eq!(Name::new(r#""foo""#), Name::new("foo"));
68+
}
69+
}

0 commit comments

Comments
 (0)