use std::collections::HashMap;
use std::path::Path;
use tree_sitter::{Node, Tree};
use crate::error::Result;
use crate::parser::Parser;
use crate::types::Type;
#[derive(Debug, Clone, serde::Serialize)]
pub struct Diagnostic {
pub path: String,
pub line: usize,
pub column: usize,
pub message: String,
pub severity: String,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct AnalysisResult {
pub path: String,
pub function_count: usize,
pub class_count: usize,
pub diagnostics: Vec<Diagnostic>,
}
#[allow(dead_code)]
pub struct Analyzer {
type_env: HashMap<String, Type>,
}
impl Default for Analyzer {
fn default() -> Self {
Self::new()
}
}
impl Analyzer {
pub fn new() -> Self {
Self { type_env: HashMap::new() }
}
pub fn analyze(&mut self, tree: &Tree, source: &[u8]) -> Result<()> {
let root_node = tree.root_node();
self.visit_node(&root_node, source)?;
Ok(())
}
#[allow(clippy::only_used_in_recursion)]
fn visit_node(&mut self, _node: &Node, _source: &[u8]) -> Result<()> {
let mut cursor = _node.walk();
for child in _node.children(&mut cursor) {
self.visit_node(&child, _source)?;
}
Ok(())
}
#[allow(dead_code)]
fn infer_expression_type(&self, _node: &Node, _source: &[u8]) -> Result<Type> {
Ok(Type::Unknown)
}
}
impl Analyzer {
pub fn analyze_python_file(path: &Path) -> Result<AnalysisResult> {
let mut parser = Parser::new()?;
let tree = parser.parse_file(path)?;
let root = tree.root_node();
let mut cursor = root.walk();
let mut function_count = 0usize;
let mut class_count = 0usize;
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let mut stack: Vec<Node> = Vec::new();
for child in root.children(&mut cursor) {
stack.push(child);
}
while let Some(node) = stack.pop() {
let kind = node.kind();
match kind {
"function_definition" => {
function_count += 1;
if let Some(params) = node.child_by_field_name("parameters") {
let mut c = params.walk();
for p in params.children(&mut c) {
let p_kind = p.kind();
let is_typed = p_kind == "typed_parameter";
if !is_typed
&& (p_kind == "identifier" || p_kind == "default_parameter")
{
let pos = p.start_position();
diagnostics.push(Diagnostic {
path: path.to_string_lossy().to_string(),
line: pos.row,
column: pos.column,
message: "Missing type annotation for parameter".to_string(),
severity: "warning".to_string(),
});
}
}
}
if node.child_by_field_name("return_type").is_none() {
let pos = node.start_position();
diagnostics.push(Diagnostic {
path: path.to_string_lossy().to_string(),
line: pos.row,
column: pos.column,
message: "Missing return type annotation".to_string(),
severity: "warning".to_string(),
});
}
},
"class_definition" => class_count += 1,
_ => {},
}
let mut child_cursor = node.walk();
for child in node.children(&mut child_cursor) {
stack.push(child);
}
}
Ok(AnalysisResult {
path: path.to_string_lossy().to_string(),
function_count,
class_count,
diagnostics,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyzer_initialization() {
let analyzer = Analyzer::new();
assert!(analyzer.type_env.is_empty());
}
}