pub mod doc;
mod sql;
#[doc(inline)]
pub use doc::{print, Doc, PrintOptions};
pub use sql_dialect_fmt_syntax::Dialect;
use sql::Ctx;
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct FormatOptions {
pub line_width: usize,
pub indent_width: usize,
pub uppercase_keywords: bool,
pub dialect: Dialect,
}
impl Default for FormatOptions {
fn default() -> Self {
FormatOptions {
line_width: 100,
indent_width: 4,
uppercase_keywords: true,
dialect: Dialect::Snowflake,
}
}
}
impl FormatOptions {
#[must_use]
pub fn with_line_width(mut self, line_width: usize) -> Self {
self.line_width = line_width;
self
}
#[must_use]
pub fn with_indent_width(mut self, indent_width: usize) -> Self {
self.indent_width = indent_width;
self
}
#[must_use]
pub fn with_uppercase_keywords(mut self, uppercase_keywords: bool) -> Self {
self.uppercase_keywords = uppercase_keywords;
self
}
#[must_use]
pub fn with_dialect(mut self, dialect: Dialect) -> Self {
self.dialect = dialect;
self
}
fn print_options(&self) -> PrintOptions {
PrintOptions {
line_width: self.line_width,
indent_width: self.indent_width,
}
}
fn ctx(&self) -> Ctx {
Ctx {
uppercase_keywords: self.uppercase_keywords,
line_width: self.line_width,
indent_width: self.indent_width,
dialect: self.dialect,
}
}
}
pub fn format(source: &str, options: &FormatOptions) -> String {
let ctx = options.ctx();
let lexed = sql_dialect_fmt_lexer::tokenize_for_dialect(source, ctx.dialect);
if !lexed.errors.is_empty() {
return source.to_string();
}
if lexed
.tokens
.iter()
.any(|token| !token.kind.is_trivia() && multiline_token_has_line_trailing_space(token.text))
{
return source.to_string();
}
let parse = sql_dialect_fmt_parser::parse_with_dialect(source, ctx.dialect);
if !parse.errors().is_empty() {
return source.to_string();
}
let root = parse.syntax();
let doc = sql::lower_source(&root, ctx);
print(&doc, &options.print_options())
}
pub(crate) fn multiline_token_has_line_trailing_space(text: &str) -> bool {
text.lines()
.any(|line| line.ends_with(' ') || line.ends_with('\t'))
}