From 975b14280b9037b2b45678b60234b4bf3bf79813 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Fri, 23 Jan 2026 22:38:31 -0500 Subject: [PATCH] fmt: initial test using tiny_pretty --- Cargo.lock | 16 ++++ Cargo.toml | 1 + crates/squawk_fmt/Cargo.toml | 22 ++++++ crates/squawk_fmt/src/lib.rs | 143 +++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 crates/squawk_fmt/Cargo.toml create mode 100644 crates/squawk_fmt/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 98de395e..bac9a3b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1909,6 +1909,16 @@ dependencies = [ "toml", ] +[[package]] +name = "squawk-fmt" +version = "2.37.0" +dependencies = [ + "insta", + "itertools 0.14.0", + "squawk-syntax", + "tiny_pretty", +] + [[package]] name = "squawk-github" version = "2.37.0" @@ -2242,6 +2252,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny_pretty" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650d82e943da333637be9f1567d33d605e76810a26464edfd7ae74f7ef181e95" + [[package]] name = "tinystr" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 08d5e1fb..1aac0e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ serde_repr = "0.1" regex = "1.11.1" simplelog = "0.12.0" tempfile = "3.21.0" +tiny_pretty = "0.2.1" toml = "0.5.9" dir-test = "0.4" drop_bomb = "0.1.5" diff --git a/crates/squawk_fmt/Cargo.toml b/crates/squawk_fmt/Cargo.toml new file mode 100644 index 00000000..c9f41f0b --- /dev/null +++ b/crates/squawk_fmt/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "squawk-fmt" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +description.workspace = true +documentation.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +tiny_pretty.workspace = true +itertools.workspace = true +squawk-syntax.workspace = true + +[dev-dependencies] +insta.workspace = true + +[lints] +workspace = true diff --git a/crates/squawk_fmt/src/lib.rs b/crates/squawk_fmt/src/lib.rs new file mode 100644 index 00000000..8971ab22 --- /dev/null +++ b/crates/squawk_fmt/src/lib.rs @@ -0,0 +1,143 @@ +use itertools::Itertools; +use squawk_syntax::ast::{self, AstNode}; +use tiny_pretty::Doc; +use tiny_pretty::{PrintOptions, print}; + +fn build_source_file(source_file: &ast::SourceFile) -> Doc<'_> { + let mut doc = Doc::nil(); + for stmt in source_file.stmts() { + match stmt { + ast::Stmt::Select(select) => { + doc = doc.append(build_select_doc(select)); + } + ast::Stmt::CreateTable(create_table) => { + doc = doc.append(build_create_table(create_table)) + } + _ => (), + } + doc = doc + .append(Doc::text(";")) + .append(Doc::empty_line()) + .append(Doc::empty_line()); + } + doc +} + +fn build_create_table<'a>(create_table: ast::CreateTable) -> Doc<'a> { + Doc::text("create") + .append(Doc::space()) + .append(Doc::text("table")) + .append(Doc::space()) + .append(Doc::text( + create_table.path().map(|x| x.syntax().to_string()).unwrap(), + )) + .append(Doc::text("(")) + .append( + Doc::line_or_nil() + .append(Doc::list( + Itertools::intersperse( + create_table + .table_arg_list() + .unwrap() + .args() + .map(build_table_arg), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + )) + .nest(2) + .append(Doc::line_or_nil()) + .group(), + ) + .append(Doc::text(")")) +} + +fn build_table_arg<'a>(create_table: ast::TableArg) -> Doc<'a> { + match create_table { + ast::TableArg::Column(column) => Doc::text(column.name().unwrap().syntax().to_string()) + .append(Doc::space()) + .append(Doc::text(column.ty().unwrap().syntax().to_string())), + ast::TableArg::LikeClause(_like_clause) => todo!(), + ast::TableArg::TableConstraint(_table_constraint) => todo!(), + } +} + +fn build_select_doc<'a>(select: ast::Select) -> Doc<'a> { + let mut doc = Doc::text("select").append(Doc::space()); + + if let Some(targets) = select + .select_clause() + .and_then(|x| x.target_list()) + .map(|x| x.targets()) + { + doc = doc + .append( + Doc::line_or_nil().append(Doc::list( + Itertools::intersperse( + targets.flat_map(|x| Some(Doc::text(x.expr()?.syntax().to_string()))), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + )), + ) + .nest(2); + } + + if let Some(from) = &select.from_clause() { + doc = doc.append( + Doc::line_or_space() + .append(Doc::text("from")) + .append(Doc::space()) + .append(Doc::text( + from.from_items().next().unwrap().syntax().to_string(), + )), + ); + } + + if let Some(group) = &select.group_by_clause() { + doc = doc.append( + Doc::line_or_space() + .append(Doc::text("group by")) + .append(Doc::space()) + .append(Doc::text( + group.group_by_list().unwrap().syntax().to_string(), + )), + ); + } + + doc.group() +} + +pub fn fmt(text: &str) -> String { + let parse = ast::SourceFile::parse(text); + let file = parse.tree(); + let doc = build_source_file(&file); + print(&doc, &PrintOptions::default()) +} + +#[cfg(test)] +mod tests { + use super::*; + use insta::assert_snapshot; + + #[test] + fn select() { + assert_snapshot!(fmt(" +select a(), date_trunc(1, 2), foo(), avg(a - b), bar(carrot), buzz(potato), foo.b from t group by c; +create table t(a int, b text); +"), @r" + select + a(), + date_trunc(1, 2), + foo(), + avg(a - b), + bar(carrot), + buzz(potato), + foo.b + from t + group by c; + + create table t(a int, b text); + "); + } +}