use crate::error::Error;
pub struct FileEmitter {
sqlc_version: String,
plugin_version: String,
items: Vec<proc_macro2::TokenStream>,
}
impl FileEmitter {
pub fn new(sqlc_version: &str, plugin_version: &str) -> Self {
Self {
sqlc_version: sqlc_version.to_string(),
plugin_version: plugin_version.to_string(),
items: Vec::new(),
}
}
pub fn push(&mut self, tokens: proc_macro2::TokenStream) {
self.items.push(tokens);
}
pub fn finish(self) -> Result<String, Error> {
use quote::quote;
let items = &self.items;
let combined = quote! { #(#items)* };
let file: syn::File = syn::parse2(combined)
.map_err(|e| Error::Codegen(format!("token stream parse failed: {e}")))?;
let formatted = prettyplease::unparse(&file);
let header = format!(
"// Code generated by sqlc-gen-sqlx v{}. DO NOT EDIT.\n\
// sqlc version: {}\n\n\
#![allow(\n dead_code,\n reason = \"generated queries may expose items a caller does not use\"\n)]\n\n",
self.plugin_version, self.sqlc_version,
);
Ok(format!("{header}{formatted}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emits_valid_rust() {
let mut e = FileEmitter::new("0.0.0-test", "sqlc-test");
e.push(quote::quote! { pub struct Foo { pub x: i32 } });
let code = e.finish().unwrap();
assert!(code.contains("pub struct Foo"));
assert!(code.contains("pub x: i32"));
assert!(code.contains("DO NOT EDIT"));
assert!(code.contains("sqlc-gen-sqlx vsqlc-test"));
assert!(code.contains("sqlc version: 0.0.0-test"));
assert!(code.contains(
"#![allow(\n dead_code,\n reason = \"generated queries may expose items a caller does not use\"\n)]"
));
}
#[test]
fn emits_empty_file_with_header() {
let e = FileEmitter::new("1.0.0", "sqlc");
let code = e.finish().unwrap();
assert!(code.contains("DO NOT EDIT"));
}
}