use cratestack_core::SourceSpan;
use crate::diagnostics::SchemaError;
use crate::line_helpers::{Line, trimmed_span};
pub(super) const SQL_ATTRS: &[&str] = &["@@server_sql", "@@embedded_sql", "@@sql"];
pub(super) fn collect_attribute_text(
lines: &[Line<'_>],
start: usize,
construct: &str,
) -> Result<(String, SourceSpan, usize), SchemaError> {
let first = &lines[start];
let trimmed = first.trimmed;
let opens_multiline_sql = SQL_ATTRS.iter().any(|prefix| trimmed.starts_with(prefix))
&& trimmed.contains("(\"\"\"")
&& !single_line_triple_closed(trimmed);
if !opens_multiline_sql {
return Ok((trimmed.to_owned(), trimmed_span(first), start + 1));
}
let mut buffer = first.raw.to_owned();
let mut cursor = start + 1;
while cursor < lines.len() {
let line = &lines[cursor];
buffer.push('\n');
buffer.push_str(line.raw);
if line.raw.contains("\"\"\")") {
let span = SourceSpan {
start: first.start + leading_ws(first.raw),
end: line.start + line.raw.len(),
line: first.number,
};
return Ok((buffer.trim().to_owned(), span, cursor + 1));
}
cursor += 1;
}
Err(SchemaError::new(
format!("unterminated `\"\"\"` SQL body in {construct} attribute"),
first.start..first.start + first.raw.len(),
first.number,
))
}
fn single_line_triple_closed(trimmed: &str) -> bool {
let after_open = match trimmed.split_once("(\"\"\"") {
Some((_, rest)) => rest,
None => return false,
};
after_open.contains("\"\"\"")
}
fn leading_ws(raw: &str) -> usize {
raw.bytes()
.take_while(|byte| byte.is_ascii_whitespace())
.count()
}