use crate::error::{McpError, McpResult};
use crate::protocol::{Position, ToolCallResult};
use crate::tools::text_response;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
use tokio::sync::Mutex;
use windjammer::lexer::Lexer;
use windjammer::parser::{Expression, Parser};
use windjammer_lsp::database::WindjammerDatabase;
#[derive(Debug, Serialize, Deserialize)]
pub struct InlineVariableRequest {
pub code: String,
pub position: Position,
pub variable_name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct InlineVariableResponse {
pub success: bool,
pub refactored_code: Option<String>,
pub occurrences_replaced: Option<usize>,
pub variable_name: Option<String>,
pub error: Option<String>,
}
pub async fn handle(
_db: Arc<Mutex<WindjammerDatabase>>,
arguments: Value,
) -> McpResult<ToolCallResult> {
let request: InlineVariableRequest =
serde_json::from_value(arguments).map_err(|e| McpError::ValidationError {
field: "arguments".to_string(),
message: e.to_string(),
})?;
let mut lexer = Lexer::new(&request.code);
let tokens = lexer.tokenize_with_locations();
let mut parser = Parser::new(tokens);
let parse_result = parser.parse();
let program = match parse_result {
Ok(prog) => prog,
Err(e) => {
let response = InlineVariableResponse {
success: false,
refactored_code: None,
occurrences_replaced: None,
variable_name: None,
error: Some(format!("Parse error: {}", e)),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
};
let var_info = find_variable_at_position(&program, &request.position);
let (var_name, var_value) = match var_info {
Some((name, value)) => (name, value),
None => {
let response = InlineVariableResponse {
success: false,
refactored_code: None,
occurrences_replaced: None,
variable_name: None,
error: Some("No variable found at position".to_string()),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
};
if !is_safe_to_inline(&var_value) {
let response = InlineVariableResponse {
success: false,
refactored_code: None,
occurrences_replaced: None,
variable_name: Some(var_name.clone()),
error: Some("Variable has side effects or is too complex to inline safely".to_string()),
};
return Ok(text_response(&serde_json::to_string(&response)?));
}
let occurrences = count_variable_uses(&program, &var_name);
let refactored_code = format!(
"// TODO: Implement full AST manipulation\n// Would inline variable '{}' ({} occurrences)",
var_name, occurrences
);
let response = InlineVariableResponse {
success: true,
refactored_code: Some(refactored_code),
occurrences_replaced: Some(occurrences),
variable_name: Some(var_name),
error: None,
};
Ok(text_response(&serde_json::to_string(&response)?))
}
fn find_variable_at_position(
_program: &windjammer::parser::Program,
_position: &Position,
) -> Option<(String, Expression)> {
None
}
fn is_safe_to_inline(expr: &Expression) -> bool {
match expr {
Expression::Literal {
value: _,
location: _,
}
| Expression::Identifier {
name: _,
location: _,
} => true,
Expression::Binary { left, right, .. } => {
is_safe_to_inline(left) && is_safe_to_inline(right)
}
Expression::Unary { operand, .. } => is_safe_to_inline(operand),
Expression::FieldAccess { object, .. } => is_safe_to_inline(object),
Expression::Call { .. } | Expression::MethodCall { .. } => false,
Expression::Await {
expr: _,
location: _,
}
| Expression::ChannelSend { .. }
| Expression::ChannelRecv {
channel: _,
location: _,
} => false,
Expression::MacroInvocation { .. } => false,
Expression::Block {
statements: _,
location: _,
} => false,
_ => false,
}
}
fn count_variable_uses(_program: &windjammer::parser::Program, _var_name: &str) -> usize {
0
}
#[cfg(test)]
mod tests {
use super::*;
use windjammer::parser::Literal;
#[tokio::test]
async fn test_inline_variable_basic() {
let db = Arc::new(Mutex::new(WindjammerDatabase::new()));
let args = serde_json::json!({
"code": "fn main() {\n let x = 42;\n println!(\"{}\", x);\n}",
"position": { "line": 1, "column": 8 }
});
let result = handle(db, args).await;
assert!(result.is_ok());
}
#[test]
fn test_is_safe_to_inline() {
assert!(is_safe_to_inline(&Expression::Literal {
value: Literal::Int(42),
location: None
}));
assert!(is_safe_to_inline(&Expression::Identifier {
name: "x".to_string(),
location: None
}));
assert!(!is_safe_to_inline(&Expression::Call {
function: Box::new(Expression::Identifier {
name: "foo".to_string(),
location: None
}),
arguments: vec![],
location: None,
}));
assert!(!is_safe_to_inline(&Expression::MacroInvocation {
name: "println".to_string(),
args: vec![],
delimiter: windjammer::parser::MacroDelimiter::Parens,
location: None,
}));
}
}