diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index a99533ea..5dea8990 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -1,6 +1,7 @@ use itertools::Itertools; use rowan::Direction; use squawk_syntax::ast::{self, AstNode}; +use squawk_syntax::quote::quote_column_alias; use squawk_syntax::{SyntaxKind, SyntaxNode, SyntaxToken}; use tiny_pretty::Doc; use tiny_pretty::{PrintOptions, print}; @@ -445,6 +446,10 @@ fn build_literal<'a>(lit: ast::Literal) -> Doc<'a> { Doc::text(lit.syntax().to_string()) } +fn build_name<'a>(name: ast::Name) -> Doc<'a> { + Doc::text(quote_column_alias(&name.text())) +} + fn build_type<'a>(ty: ast::Type) -> Doc<'a> { Doc::text(ty.syntax().to_string()) } @@ -538,10 +543,7 @@ fn build_target<'a>(target: ast::Target) -> Option> { } if let Some(name) = as_name.name() { - // TODO: quoting or not? - doc = doc - .append(Doc::space()) - .append(Doc::text(name.syntax().to_string())); + doc = doc.append(Doc::space()).append(build_name(name)); } } diff --git a/crates/squawk_fmt/tests/after/select.snap b/crates/squawk_fmt/tests/after/select.snap index d1440041..da394e5d 100644 --- a/crates/squawk_fmt/tests/after/select.snap +++ b/crates/squawk_fmt/tests/after/select.snap @@ -9,3 +9,9 @@ select now(); select 'really long string ', 'another really long string'; + +select + foo as "Quoted Alias" +from "Quoted Table"; + +select 1 as foo; diff --git a/crates/squawk_fmt/tests/before/select.sql b/crates/squawk_fmt/tests/before/select.sql index d3b14bd4..ec9ba398 100644 --- a/crates/squawk_fmt/tests/before/select.sql +++ b/crates/squawk_fmt/tests/before/select.sql @@ -3,3 +3,7 @@ select 'hello'; select now(); select 'really long string ', 'another really long string'; + +select foo as "Quoted Alias" from "Quoted Table"; + +select 1 as "foo"; diff --git a/crates/squawk_fmt/tests/tests.rs b/crates/squawk_fmt/tests/tests.rs index fa15d63d..67ebcf62 100644 --- a/crates/squawk_fmt/tests/tests.rs +++ b/crates/squawk_fmt/tests/tests.rs @@ -42,6 +42,31 @@ fn meaningful_tokens(text: &str) -> Vec<(TokenKind, &str)> { tokens } +fn tokens_equivalent(before: (TokenKind, &str), after: (TokenKind, &str)) -> bool { + let (bkind, btext) = before; + let (akind, atext) = after; + + if bkind == akind { + return btext.eq_ignore_ascii_case(atext); + } + + // We convert `select 1 "foo"` to `select 1 foo` so we need to do some quote + // munging + let unquote = |kind: TokenKind, text: &str| match kind { + TokenKind::QuotedIdent { .. } => text + .strip_prefix('"') + .and_then(|t| t.strip_suffix('"')) + .map(|t| t.to_string()), + TokenKind::Ident => Some(text.to_string()), + _ => None, + }; + + match (unquote(bkind, btext), unquote(akind, atext)) { + (Some(b), Some(a)) => b.eq_ignore_ascii_case(&a), + _ => false, + } +} + fn assert_no_dropped_tokens(before: &str, after: &str) { let before_tokens = meaningful_tokens(before); let after_tokens = meaningful_tokens(after); @@ -49,11 +74,11 @@ fn assert_no_dropped_tokens(before: &str, after: &str) { let before_len = before_tokens.len(); let after_len = after_tokens.len(); - for (i, ((bkind, btext), (akind, atext))) in + for (i, (&(bkind, btext), &(akind, atext))) in before_tokens.iter().zip(after_tokens.iter()).enumerate() { assert!( - bkind == akind && btext.eq_ignore_ascii_case(atext), + tokens_equivalent((bkind, btext), (akind, atext)), "token mismatch at position {i}:\n before: {bkind:?} {btext:?}\n after: {akind:?} {atext:?}" ); }