use error_stack::{IntoReport, Result, ResultExt};
use inflector::Inflector;
use serde::{Deserialize, Serialize};
use crate::batbelt::git::git_action::GitAction;
use crate::batbelt::metadata::context_accounts_metadata::ContextAccountsMetadata;
use crate::batbelt::metadata::structs_source_code_metadata::StructMetadataType;
use crate::batbelt::metadata::{BatMetadata, BatMetadataParser, SourceCodeMetadata};
use crate::batbelt::parser::entrypoint_parser::EntrypointParser;
use crate::batbelt::parser::function_parser::FunctionParser;
use crate::batbelt::parser::pinocchio_context_accounts_parser;
use crate::batbelt::parser::solana_account_parser::{SolanaAccountParser, SolanaAccountType};
use crate::batbelt::path::BatFile;
use crate::batbelt::sonar::{BatSonar, SonarResultType};
use crate::batbelt::templates::code_overhaul_template::CoderOverhaulTemplatePlaceholders::{
CompleteWithNotes, CompleteWithTheRestOfStateChanges,
};
use crate::batbelt::templates::{TemplateError, TemplateResult};
use crate::batbelt::{BatEnumerator, ShareableData};
pub struct CodeOverhaulTemplate {
pub entrypoint_name: String,
pub entrypoint_parser: Option<EntrypointParser>,
}
impl CodeOverhaulTemplate {
pub fn new(entrypoint_name: &str, initialized: bool) -> Result<Self, TemplateError> {
let entrypoint_parser = if initialized {
let ep_parser =
EntrypointParser::new_from_name(entrypoint_name).change_context(TemplateError)?;
Some(ep_parser)
} else {
None
};
Ok(Self {
entrypoint_name: entrypoint_name.to_string(),
entrypoint_parser,
})
}
pub fn get_markdown_content(&self) -> TemplateResult<String> {
let state_changes_content = CodeOverhaulSection::StateChanges
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let notes_content = CodeOverhaulSection::Notes
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let signers_content = CodeOverhaulSection::Signers
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let function_parameters_content = CodeOverhaulSection::DependencyFunctionParameters
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let context_accounts_content = CodeOverhaulSection::ContextAccounts
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let validations_content = CodeOverhaulSection::Validations
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
let miro_frame_url_content = CodeOverhaulSection::MiroFrameUrl
.get_section_content_for_start_co_file(self.entrypoint_parser.clone())?;
Ok(format!(
"{state_changes_content}\
\n\
\n\
{notes_content}\
\n\
\n\
{signers_content}\
\n\
\n\
{function_parameters_content}\
\n\
\n\
{context_accounts_content}\
\n\
\n\
{validations_content}\
\n\
\n\
{miro_frame_url_content}
",
))
}
}
#[derive(
Default,
Debug,
Serialize,
Deserialize,
Clone,
strum_macros::EnumIter,
strum_macros::Display,
PartialOrd,
PartialEq,
)]
pub enum CodeOverhaulSection {
#[default]
StateChanges,
Notes,
Signers,
DependencyFunctionParameters,
ContextAccounts,
Validations,
MiroFrameUrl,
}
impl BatEnumerator for CodeOverhaulSection {}
impl CodeOverhaulSection {
pub fn to_markdown_header(&self) -> String {
format!("# {}:", self.to_string().to_sentence_case())
}
pub fn to_title(&self) -> String {
format!("{}:", self.to_string().to_sentence_case())
}
pub fn get_section_content_for_start_co_file(
&self,
ep_parser: Option<EntrypointParser>,
) -> TemplateResult<String> {
let section_content = if ep_parser.is_some() {
let entrypoint_parser = ep_parser.unwrap();
match self {
CodeOverhaulSection::StateChanges => {
self.get_state_changes_content(entrypoint_parser)?
}
CodeOverhaulSection::Notes => self.get_notes_content(entrypoint_parser)?,
CodeOverhaulSection::Signers => self.get_signers_section_content(entrypoint_parser),
CodeOverhaulSection::DependencyFunctionParameters => {
self.get_dependency_function_parameters_section_content(entrypoint_parser)?
}
CodeOverhaulSection::ContextAccounts => {
self.get_context_account_section_content(entrypoint_parser)
}
CodeOverhaulSection::Validations => {
self.get_validations_section_content(entrypoint_parser)?
}
CodeOverhaulSection::MiroFrameUrl => {
CoderOverhaulTemplatePlaceholders::CompleteWithMiroFrameUrl.to_placeholder()
}
}
} else {
"".to_string()
};
Ok(format!(
"{}\n\n{}",
self.to_markdown_header(),
section_content
))
}
fn get_notes_content(&self, entry_point_parser: EntrypointParser) -> TemplateResult<String> {
let context_accounts = match entry_point_parser.context_accounts {
Some(ca) => ca,
None => return Ok(format!("- {}", CompleteWithNotes.to_placeholder())),
};
let context_accounts_struct_source_code_metadata_id = context_accounts.metadata_id;
let context_accounts_metadata =
ContextAccountsMetadata::find_context_accounts_metadata_by_struct_metadata_id(
context_accounts_struct_source_code_metadata_id,
)
.change_context(TemplateError)?;
let context_accounts_sc_metadata = SourceCodeMetadata::find_struct(
context_accounts_metadata.name.clone(),
StructMetadataType::ContextAccounts,
)
.change_context(TemplateError)?;
let ca_sc_metadata_file_content = BatFile::Generic {
file_path: context_accounts_sc_metadata.path.clone(),
}
.read_content(false)
.change_context(TemplateError)?;
let ca_sc_file_content_lines = ca_sc_metadata_file_content.lines();
let ca_info_with_validation = context_accounts_metadata
.context_accounts_info
.clone()
.into_iter()
.filter_map(|ca_info| {
if !ca_info.validations.is_empty() {
Some(ca_info.validations.clone())
} else {
None
}
});
if ca_info_with_validation.clone().count() == 0 {
return Ok(format!("- {}", CompleteWithNotes.to_placeholder()));
}
let mut result = vec![];
result.push("- [ ] check constraints:".to_string());
for ca_info_validations_vec in ca_info_with_validation.clone() {
for ca_info_validation in ca_info_validations_vec.clone() {
let validation_line = ca_sc_file_content_lines
.clone()
.position(|line| line.contains(&ca_info_validation))
.ok_or(TemplateError)
.into_report()?;
let shared_permalink = ShareableData::new(String::new());
GitAction::GetRepositoryPermalink {
file_path: context_accounts_sc_metadata.path.clone(),
start_line_index: validation_line + 1,
permalink: shared_permalink.original,
}
.execute_action()
.change_context(TemplateError)?;
result.push(format!(
" - [ ] [{}]({}), [ref]({})",
ca_info_validation,
validation_line + 1,
*shared_permalink.cloned.borrow()
));
}
}
result.push(format!("- {}", CompleteWithNotes.to_placeholder()));
Ok(result.join("\n"))
}
fn get_state_changes_content(
&self,
entry_point_parser: EntrypointParser,
) -> TemplateResult<String> {
let context_accounts = match entry_point_parser.context_accounts {
Some(ca) => ca,
None => {
return Ok(format!(
"- `{}`",
CompleteWithTheRestOfStateChanges.to_placeholder()
))
}
};
let bat_metadata = BatMetadata::read_metadata().change_context(TemplateError)?;
let mut state_changes_content_vec = vec![];
let context_accounts_metadata = bat_metadata
.get_context_accounts_metadata_by_struct_source_code_metadata_id(
context_accounts.metadata_id,
)
.change_context(TemplateError)?;
let init_accounts = context_accounts_metadata
.context_accounts_info
.clone()
.into_iter()
.filter(|ca_info| ca_info.is_init)
.collect::<Vec<_>>();
for acc in init_accounts {
state_changes_content_vec.push(format!(
"- Initializes `{}`[{}], funded by `{}`",
acc.account_name, acc.account_struct_name, acc.rent_exemption_account
))
}
let close_accounts = context_accounts_metadata
.context_accounts_info
.clone()
.into_iter()
.filter(|ca_info| ca_info.is_close)
.collect::<Vec<_>>();
for acc in close_accounts {
state_changes_content_vec.push(format!(
"- Closes `{}`[{}]. Rent exemption goes to `{}`",
acc.account_name, acc.account_struct_name, acc.rent_exemption_account
))
}
let mut_program_state_accounts = context_accounts_metadata
.clone()
.context_accounts_info
.into_iter()
.filter(|ca_info| {
ca_info.is_mut
&& !ca_info.is_close
&& !ca_info.is_init
&& ca_info.solana_account_type == SolanaAccountType::ProgramStateAccount
});
for mut_program_state_acc in mut_program_state_accounts {
match SolanaAccountParser::new_from_struct_name_and_solana_account_type(
mut_program_state_acc.clone().account_struct_name,
mut_program_state_acc.clone().solana_account_type,
) {
Ok(solana_acc_parser) => {
state_changes_content_vec.push(format!(
"- Updates `{}`[{}]:\n{}",
mut_program_state_acc.clone().account_name,
mut_program_state_acc.clone().account_struct_name,
solana_acc_parser
.accounts
.clone()
.into_iter()
.map(|acc_parser| format!(
" - `{}.{}`[{}]",
mut_program_state_acc.clone().account_name,
acc_parser.account_name,
acc_parser.account_type
))
.collect::<Vec<_>>()
.join("\n")
));
}
Err(_) => {
state_changes_content_vec.push(format!(
"- Updates `{}`[{}]",
mut_program_state_acc.account_name,
mut_program_state_acc.account_struct_name,
));
}
}
}
let mut_unknown_accounts = context_accounts_metadata
.clone()
.context_accounts_info
.into_iter()
.filter(|ca_info| {
ca_info.is_mut
&& (ca_info.solana_account_type == SolanaAccountType::UncheckedAccount
|| ca_info.solana_account_type == SolanaAccountType::Other)
});
for mut_unkown_account in mut_unknown_accounts {
state_changes_content_vec.push(format!(
"- Updates `{}`[{}]",
mut_unkown_account.clone().account_name,
mut_unkown_account.clone().account_struct_name,
));
}
let mut_token_accounts = context_accounts_metadata
.clone()
.context_accounts_info
.into_iter()
.filter(|ca_info| {
ca_info.is_mut && ca_info.solana_account_type == SolanaAccountType::TokenAccount
})
.collect::<Vec<_>>();
for (mut_token_account_index, mut_token_account) in
mut_token_accounts.clone().into_iter().enumerate()
{
let mut destination_index = 0;
while destination_index < mut_token_accounts.len() {
if destination_index == mut_token_account_index {
destination_index += 1;
continue;
}
state_changes_content_vec.push(format!(
"- Transfers `{}` tokens from `{}`[authority={}] to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
mut_token_accounts.clone()[destination_index].account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
destination_index += 1;
}
state_changes_content_vec.push(format!(
"- Transfers `{}` tokens from `{}`[authority={}] to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithDestinationTokenAccount
.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
destination_index = 0;
while destination_index < mut_token_accounts.len() {
if destination_index == mut_token_account_index {
destination_index += 1;
continue;
}
state_changes_content_vec.push(format!(
"- Delegates `{}` tokens from `{}`[authority={}] to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
mut_token_accounts.clone()[destination_index].account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
destination_index += 1;
}
state_changes_content_vec.push(format!(
"- Delegates `{}` tokens from `{}`[authority={}] to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithDestinationTokenAccount
.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
}
let mut_mint_accounts = context_accounts_metadata
.clone()
.context_accounts_info
.into_iter()
.filter(|ca_info| {
ca_info.is_mut && ca_info.solana_account_type == SolanaAccountType::Mint
})
.collect::<Vec<_>>();
for mut_mint_account in mut_mint_accounts {
for mut_token_account in mut_token_accounts.clone() {
state_changes_content_vec.push(format!(
"- Mints `{}` tokens from `{}` token_mint to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_mint_account.clone().account_name,
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
}
state_changes_content_vec.push(format!(
"- Mints `{}` tokens from `{}` token_mint to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_mint_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithDestinationTokenAccount
.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
for mut_token_account in mut_token_accounts.clone() {
state_changes_content_vec.push(format!(
"- Burns `{}` tokens from `{}` token_mint to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_mint_account.clone().account_name,
mut_token_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
}
state_changes_content_vec.push(format!(
"- Burns `{}` tokens from `{}` token_mint to `{}`[authority={}]",
CoderOverhaulTemplatePlaceholders::CompleteWithAmount.to_placeholder(),
mut_mint_account.clone().account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithDestinationTokenAccount
.to_placeholder(),
CoderOverhaulTemplatePlaceholders::CompleteWithTokenAuthority.to_placeholder(),
));
}
state_changes_content_vec.push(format!(
"- `{}`",
CompleteWithTheRestOfStateChanges.to_placeholder()
));
Ok(state_changes_content_vec.join("\n"))
}
fn get_validations_section_content(
&self,
entrypoint_parser: EntrypointParser,
) -> TemplateResult<String> {
log::debug!(
"get_validations_section_content entrypoint_parser \n{:#?}",
entrypoint_parser
);
let mut dependency_validations: Vec<String> = vec![];
for dep_function in &entrypoint_parser.dependencies {
let dep_if_validations = BatSonar::new_from_path_with_lines(
&dep_function.path,
dep_function.start_line_index,
dep_function.end_line_index,
SonarResultType::IfValidation,
);
let if_validations = dep_if_validations
.results
.iter()
.map(|if_validation| {
let if_in_validations =
BatSonar::new_scanned(&if_validation.content, SonarResultType::Validation);
if !if_in_validations.results.is_empty() {
if_in_validations.results
} else {
vec![]
}
})
.fold(vec![], |mut result, current| {
for res in current {
result.push(res);
}
result
});
let mut filtered_if = dep_if_validations
.results
.iter()
.filter(|if_est| {
if_validations
.clone()
.iter()
.any(|if_val| if_est.content.contains(&if_val.content))
})
.map(|result| result.content.clone())
.collect::<Vec<_>>();
let dep_validations_sonar = BatSonar::new_from_path_with_lines(
&dep_function.path,
dep_function.start_line_index,
dep_function.end_line_index,
SonarResultType::Validation,
);
let mut filtered_dep = if if_validations.is_empty() {
dep_validations_sonar
.results
.iter()
.map(|result| result.content.clone())
.collect::<Vec<_>>()
} else {
dep_validations_sonar
.results
.iter()
.filter(|validation| {
!dep_if_validations
.results
.iter()
.any(|if_val| if_val.content.contains(&validation.content.to_string()))
})
.map(|val| val.content.clone())
.collect::<Vec<_>>()
};
if !filtered_if.is_empty() || !filtered_dep.is_empty() {
let relative_path = dep_function.path.trim_start_matches("../").to_string();
dependency_validations.push(format!(
"// {} — {}:{}:",
dep_function.name, relative_path, dep_function.start_line_index
));
dependency_validations.append(&mut filtered_if);
dependency_validations.append(&mut filtered_dep);
}
}
let mut ca_accounts_results: Vec<String> = Vec::new();
if let Some(ref ca) = entrypoint_parser.context_accounts {
let bat_metadata = BatMetadata::read_metadata().change_context(TemplateError)?;
let context_accounts_metadata = bat_metadata
.get_context_accounts_metadata_by_struct_source_code_metadata_id(
ca.metadata_id.clone(),
)
.change_context(TemplateError)?;
log::debug!(
"context_accounts_metadata:\n{:#?}",
context_accounts_metadata
);
ca_accounts_results = context_accounts_metadata
.context_accounts_info
.into_iter()
.filter_map(|ca_metadata| {
if !ca_metadata.validations.is_empty() {
let trailing_str = " ".to_string();
let account_line = if ca_metadata.lifetime_name.is_empty() {
format!(
"{}pub {}: {},",
trailing_str,
ca_metadata.account_name,
ca_metadata.account_struct_name
)
} else if ca_metadata.account_wrapper_name
== ca_metadata.account_struct_name
{
format!(
"{}pub {}: {}<{}>,",
trailing_str,
ca_metadata.account_name,
ca_metadata.account_struct_name,
ca_metadata.lifetime_name
)
} else {
format!(
"{}pub {}: {}<{}, {}>,",
trailing_str,
ca_metadata.account_name,
ca_metadata.account_wrapper_name,
ca_metadata.lifetime_name,
ca_metadata.account_struct_name
)
};
let result = format!(
"{}#[account(\n{}\n{})]\n{}",
trailing_str,
ca_metadata
.validations
.into_iter()
.map(|validation| {
format!(
"{}\t{},",
trailing_str.clone(),
validation.trim_end_matches(',')
)
})
.collect::<Vec<_>>()
.join("\n"),
trailing_str,
account_line
);
Some(result)
} else {
None
}
})
.collect::<Vec<_>>();
log::debug!("ca_accounts_results:\n{:#?}", ca_accounts_results.clone());
}
let mut validations_vec: Vec<String> = vec![];
validations_vec.append(&mut ca_accounts_results);
validations_vec.append(&mut dependency_validations);
let validations_content = if validations_vec.is_empty() {
format!(
"- {}",
CoderOverhaulTemplatePlaceholders::NoValidationsDetected.to_placeholder()
)
} else {
validations_vec
.iter()
.map(|validation| format!("```rust\n{}\n```", validation))
.collect::<Vec<_>>()
.join("\n")
};
Ok(validations_content)
}
fn get_signers_section_content(&self, entrypoint_parser: EntrypointParser) -> String {
let context_accounts = match entrypoint_parser.context_accounts {
Some(ref ca) => ca,
None => {
return CoderOverhaulTemplatePlaceholders::PermissionlessFunction
.to_placeholder()
.to_string()
}
};
eprintln!(
"[DEBUG signers] CA name={}, path={}, metadata_id={}",
context_accounts.name, context_accounts.path, context_accounts.metadata_id
);
if let Ok(bat_metadata) = BatMetadata::read_metadata() {
eprintln!("[DEBUG signers] metadata loaded OK");
match bat_metadata.get_context_accounts_metadata_by_struct_source_code_metadata_id(
context_accounts.metadata_id.clone(),
) {
Ok(ca_metadata) => {
eprintln!(
"[DEBUG signers] CA metadata found, accounts: {:?}",
ca_metadata
.context_accounts_info
.iter()
.map(|i| format!(
"{}:{:?}/{}",
i.account_name, i.solana_account_type, i.account_wrapper_name
))
.collect::<Vec<_>>()
);
let signer_accounts: Vec<_> = ca_metadata
.context_accounts_info
.iter()
.filter(|info| {
info.solana_account_type == SolanaAccountType::Signer
|| info.account_wrapper_name == "Signer"
})
.collect();
if !signer_accounts.is_empty() {
let signers: Vec<String> = signer_accounts
.iter()
.map(|info| {
format!(
"- {}: {}",
info.account_name,
CoderOverhaulTemplatePlaceholders::CompleteWithSignerDescription
.to_placeholder()
)
})
.collect();
return signers.join("\n");
}
}
Err(e) => {
eprintln!("[DEBUG signers] CA metadata NOT found: {:?}", e);
}
}
} else {
eprintln!("[DEBUG signers] metadata load FAILED");
}
let context_source_code = context_accounts.to_source_code_parser(None);
let context_lines = context_source_code.get_source_code_content();
eprintln!(
"[DEBUG signers] context_lines contains AccountView={}, len={}",
context_lines.contains("AccountView"),
context_lines.len()
);
eprintln!(
"[DEBUG signers] context_lines: {}",
&context_lines[..context_lines.len().min(300)]
);
if context_lines.contains("AccountView") || context_lines.contains("AccountInfo") {
eprintln!(
"[DEBUG signers] Pinocchio path, reading file: {}",
context_accounts.path
);
if let Ok(file_content) = std::fs::read_to_string(&context_accounts.path) {
eprintln!("[DEBUG signers] file read OK, len={}", file_content.len());
match pinocchio_context_accounts_parser::parse_pinocchio_context_accounts_from_source(
&file_content,
)
{
Ok(parsed_structs) => {
eprintln!("[DEBUG signers] parsed {} structs: {:?}", parsed_structs.len(), parsed_structs.iter().map(|s| &s.name).collect::<Vec<_>>());
if let Some(parsed) = parsed_structs
.iter()
.find(|s| s.name == context_accounts.name)
{
eprintln!("[DEBUG signers] found struct {}, accounts: {:?}", parsed.name, parsed.accounts.iter().map(|a| format!("{}:{}", a.field_name, a.account_wrapper_name)).collect::<Vec<_>>());
let signer_accounts: Vec<_> = parsed
.accounts
.iter()
.filter(|acc| acc.account_wrapper_name == "Signer")
.collect();
if !signer_accounts.is_empty() {
let signers: Vec<String> = signer_accounts
.iter()
.map(|acc| {
format!(
"- {}: {}",
acc.field_name,
CoderOverhaulTemplatePlaceholders::CompleteWithSignerDescription
.to_placeholder()
)
})
.collect();
return signers.join("\n");
}
}
}
Err(e) => {
eprintln!("[DEBUG signers] parse error: {:?}", e);
}
}
}
return CoderOverhaulTemplatePlaceholders::PermissionlessFunction
.to_placeholder()
.to_string();
}
let mut signers: Vec<String> = vec![];
for (line_index, line) in context_lines.lines().enumerate() {
if !line.contains("pub") {
continue;
}
let next_pub_line = context_lines
.lines()
.enumerate()
.position(|line| {
line.0 > line_index && line.1.contains("pub")
|| line.0 == context_lines.lines().count() - 1
})
.unwrap();
let mut content =
context_lines.lines().collect::<Vec<_>>()[line_index + 1..=next_pub_line].to_vec();
let has_signer = content.clone().last().unwrap().contains("Signer<");
if !has_signer {
continue;
}
let signer_name = content.clone().last().unwrap().trim().replace("pub ", "");
let signer_name = signer_name.split(':').next().unwrap();
content.pop().unwrap();
let signer_comments = content
.iter()
.filter(|line| line.trim().split(' ').next().unwrap().contains("//"))
.collect::<Vec<_>>();
if signer_comments.is_empty() {
let signer_description = format!(
"- {}: {}",
signer_name,
CoderOverhaulTemplatePlaceholders::CompleteWithSignerDescription
.to_placeholder()
);
signers.push(signer_description)
} else if signer_comments.len() == 1 {
let signer_description_comment = signer_comments[0].split("// ").last().unwrap();
let signer_description =
format!("- {}: {}", signer_name, signer_description_comment);
signers.push(signer_description);
} else {
let signer_formatted = signer_comments
.iter()
.map(|line| line.split("// ").last().unwrap().to_string())
.collect::<Vec<_>>()
.join(". ");
let signer_description = format!("- {}: {}", signer_name, signer_formatted);
signers.push(signer_description);
}
}
if signers.is_empty() {
return CoderOverhaulTemplatePlaceholders::PermissionlessFunction
.to_placeholder()
.to_string();
}
signers.join("\n")
}
fn get_context_account_section_content(&self, entrypoint_parser: EntrypointParser) -> String {
let context_accounts = match entrypoint_parser.context_accounts {
Some(ca) => ca,
None => return "No context accounts struct".to_string(),
};
let context_accounts_source_code = context_accounts.to_source_code_parser(None);
let context_accounts_content = context_accounts_source_code.get_source_code_content();
let is_pinocchio = context_accounts_content.contains("AccountView")
|| (context_accounts_content.contains("AccountInfo")
&& !context_accounts_content.contains("Account<"));
let display_content = if is_pinocchio {
context_accounts_content.clone()
} else {
let accounts = BatSonar::new_scanned(
&context_accounts_content,
SonarResultType::ContextAccountsNoValidation,
);
let accounts_string = accounts
.results
.iter()
.fold("".to_string(), |result, next| {
format!("{}\n\n{}", result, next.content)
});
let first_line = context_accounts_content.lines().next().unwrap();
let last_line = context_accounts_content.lines().last().unwrap();
format!(
"{}\n{}\n{}",
first_line,
accounts_string.trim_start_matches('\n'),
last_line,
)
};
let formatted = display_content
.lines()
.map(|line| format!(" {}", line))
.collect::<Vec<_>>()
.join("\n");
format!("{}\n{}\n{}", "```rust", formatted, "```")
}
fn get_dependency_function_parameters_section_content(
&self,
entrypoint_parser: EntrypointParser,
) -> TemplateResult<String> {
if entrypoint_parser.dependencies.is_empty() {
return Ok(
CoderOverhaulTemplatePlaceholders::NoDependencyFunctionParametersDetected
.to_placeholder()
.to_string(),
);
}
let mut all_parameters = vec![];
for dep_function in &entrypoint_parser.dependencies {
let dep_function_parser = FunctionParser::new_from_metadata(dep_function.clone())
.change_context(TemplateError)?;
let filtered_parameters = dep_function_parser
.parameters
.into_iter()
.filter(|parameter| {
let normalized =
crate::batbelt::parser::function_parser::normalize_generic_type(
¶meter.parameter_type,
);
!normalized.contains("Context<")
})
.collect::<Vec<_>>();
if !filtered_parameters.is_empty() {
all_parameters.push(format!("// {}:", dep_function.name));
for parameter in filtered_parameters {
all_parameters.push(format!(
"- {}: {}",
parameter.parameter_name,
parameter.parameter_type.trim_end_matches(',')
));
if let Ok(struct_metadata) = SourceCodeMetadata::find_struct(
parameter.parameter_type.trim_start_matches("&").to_string(),
StructMetadataType::Other,
) {
all_parameters.push(format!(
"```rust\n{}\n```",
struct_metadata
.to_source_code_parser(None)
.get_source_code_content()
.lines()
.map(|line| format!(" {line}"))
.collect::<Vec<_>>()
.join("\n")
));
}
}
}
}
let function_parameters_content = if all_parameters.is_empty() {
CoderOverhaulTemplatePlaceholders::NoDependencyFunctionParametersDetected
.to_placeholder()
.to_string()
} else {
all_parameters.join("\n")
};
Ok(function_parameters_content)
}
}
#[derive(strum_macros::Display)]
pub enum CoderOverhaulTemplatePlaceholders {
PermissionlessFunction,
NoValidationsDetected,
NoDependencyFunctionParametersDetected,
CompleteWithTheRestOfStateChanges,
CompleteWithNotes,
CompleteWithSignerDescription,
CompleteWithMiroFrameUrl,
CompleteWithDestinationTokenAccount,
CompleteWithAmount,
CompleteWithTokenAuthority,
}
impl CoderOverhaulTemplatePlaceholders {
pub fn to_placeholder(&self) -> String {
self.to_string().to_screaming_snake_case()
}
pub fn get_state_changes_checked_placeholders_vec() -> Vec<String> {
vec![
Self::CompleteWithTheRestOfStateChanges.to_placeholder(),
Self::CompleteWithAmount.to_placeholder(),
Self::CompleteWithDestinationTokenAccount.to_placeholder(),
Self::CompleteWithTokenAuthority.to_placeholder(),
]
}
}
#[test]
fn test_to_title() {
let expected = "Signers:";
let title = CodeOverhaulSection::Signers.to_title();
println!("title {:#?}", title);
assert_eq!(expected, title, "Incorrect title");
let expected = "Context accounts:";
let title = CodeOverhaulSection::ContextAccounts.to_title();
println!("title {:#?}", title);
assert_eq!(expected, title, "Incorrect title");
let expected = "Validations:";
let title = CodeOverhaulSection::Validations.to_title();
println!("title {:#?}", title);
assert_eq!(expected, title, "Incorrect title");
}