use crate::parse::traits::{Block, Edge, EdgeType, Parameter, Visibility};
use crate::parse::traits::{
CodeIntelligence, ComplexityMetrics, Error, Graph, ImportInfo, Result, SignatureInfo,
};
use tree_sitter::Parser;
pub struct JavaScriptParser;
impl Default for JavaScriptParser {
fn default() -> Self {
Self::new()
}
}
impl JavaScriptParser {
pub fn new() -> Self {
Self
}
fn extract_all_definitions(
&self,
source: &[u8],
root: tree_sitter::Node<'_>,
) -> Vec<SignatureInfo> {
let mut signatures = Vec::new();
fn visit_node(
node: &tree_sitter::Node<'_>,
source: &[u8],
signatures: &mut Vec<SignatureInfo>,
parent_path: &[String],
) {
match node.kind() {
"function_declaration" | "method_definition" | "generator_function_declaration" => {
if let Some(sig) = extract_function_signature(node, source, parent_path) {
signatures.push(sig);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
"class_declaration" | "class_expression" => {
let class_name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
if let Some(name) = class_name {
let mut class_path = parent_path.to_vec();
class_path.push(name.clone());
signatures.push(SignatureInfo {
name: name.clone(),
qualified_name: if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
},
parameters: vec![],
return_type: None,
visibility: Visibility::Public,
is_async: false,
is_method: false,
docstring: extract_docstring(node, source),
calls: vec![],
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
cyclomatic_complexity: 0,
});
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, &class_path);
}
} else {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
}
"lexical_declaration" | "variable_declaration" => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "variable_declarator" {
if let Some(name) = child
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
{
if let Some(value) = child.child_by_field_name("value") {
if value.kind() == "arrow_function"
|| value.kind() == "function_expression"
{
if let Some(sig) =
extract_function_signature(&value, source, parent_path)
{
let mut sig = sig;
sig.name = name.to_string();
sig.qualified_name = if parent_path.is_empty() {
name.to_string()
} else {
format!("{}.{}", parent_path.join("."), name)
};
signatures.push(sig);
}
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
}
}
visit_node(&root, source, &mut signatures, &[]);
signatures
}
}
impl CodeIntelligence for JavaScriptParser {
fn get_signatures(&self, source: &[u8]) -> Result<Vec<SignatureInfo>> {
let mut parser = Parser::new();
self.get_signatures_with_parser(source, &mut parser)
}
fn get_signatures_with_parser(
&self,
source: &[u8],
parser: &mut tree_sitter::Parser,
) -> Result<Vec<SignatureInfo>> {
parser
.set_language(&crate::parse::traits::languages::javascript::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse JavaScript source".to_string()))?;
let root_node = tree.root_node();
let imports = extract_js_imports(root_node, source);
let mut signatures = self.extract_all_definitions(source, root_node);
for sig in &mut signatures {
sig.imports = imports.clone();
}
Ok(signatures)
}
fn compute_cfg(&self, source: &[u8], node_id: usize) -> Result<Graph<Block, Edge>> {
let mut parser = Parser::new();
parser
.set_language(&crate::parse::traits::languages::javascript::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse JavaScript source".to_string()))?;
let root_node = tree.root_node();
let node = find_node_by_id(&root_node, node_id)
.ok_or_else(|| Error::ParseFailed(format!("Node {} not found", node_id)))?;
let mut cfg_builder = CfgBuilder::new(source);
cfg_builder.build_from_node(&node)?;
Ok(cfg_builder.finish())
}
fn extract_complexity(&self, node: &tree_sitter::Node<'_>) -> ComplexityMetrics {
let mut complexity = ComplexityMetrics {
cyclomatic: 1,
nesting_depth: 0,
line_count: 0,
token_count: 0,
};
calculate_complexity(node, &mut complexity, 0);
complexity
}
}
pub struct TypeScriptParser;
impl Default for TypeScriptParser {
fn default() -> Self {
Self::new()
}
}
impl TypeScriptParser {
pub fn new() -> Self {
Self
}
fn extract_all_definitions(
&self,
source: &[u8],
root: tree_sitter::Node<'_>,
) -> Vec<SignatureInfo> {
let mut signatures = Vec::new();
fn visit_node(
node: &tree_sitter::Node<'_>,
source: &[u8],
signatures: &mut Vec<SignatureInfo>,
parent_path: &[String],
) {
match node.kind() {
"function_declaration" | "method_definition" | "generator_function_declaration" => {
if let Some(sig) = extract_ts_function_signature(node, source, parent_path) {
signatures.push(sig);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
"interface_declaration" | "type_alias_declaration" => {
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
if let Some(type_name) = name {
signatures.push(SignatureInfo {
name: type_name.clone(),
qualified_name: if parent_path.is_empty() {
type_name.clone()
} else {
format!("{}.{}", parent_path.join("."), type_name)
},
parameters: vec![],
return_type: None,
visibility: Visibility::Public,
is_async: false,
is_method: false,
docstring: extract_docstring(node, source),
calls: vec![],
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
cyclomatic_complexity: 0,
});
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
"class_declaration" | "class_expression" => {
let class_name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
if let Some(name) = class_name {
let mut class_path = parent_path.to_vec();
class_path.push(name.clone());
signatures.push(SignatureInfo {
name: name.clone(),
qualified_name: if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
},
parameters: vec![],
return_type: None,
visibility: Visibility::Public,
is_async: false,
is_method: false,
docstring: extract_docstring(node, source),
calls: vec![],
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
cyclomatic_complexity: 0,
});
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, &class_path);
}
} else {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
}
_ => {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit_node(&child, source, signatures, parent_path);
}
}
}
}
visit_node(&root, source, &mut signatures, &[]);
signatures
}
}
impl CodeIntelligence for TypeScriptParser {
fn get_signatures(&self, source: &[u8]) -> Result<Vec<SignatureInfo>> {
let mut parser = Parser::new();
self.get_signatures_with_parser(source, &mut parser)
}
fn get_signatures_with_parser(
&self,
source: &[u8],
parser: &mut tree_sitter::Parser,
) -> Result<Vec<SignatureInfo>> {
parser
.set_language(&crate::parse::traits::languages::typescript::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse TypeScript source".to_string()))?;
let root_node = tree.root_node();
let imports = extract_js_imports(root_node, source);
let mut signatures = self.extract_all_definitions(source, root_node);
for sig in &mut signatures {
sig.imports = imports.clone();
}
Ok(signatures)
}
fn compute_cfg(&self, source: &[u8], node_id: usize) -> Result<Graph<Block, Edge>> {
let mut parser = Parser::new();
parser
.set_language(&crate::parse::traits::languages::typescript::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse TypeScript source".to_string()))?;
let root_node = tree.root_node();
let node = find_node_by_id(&root_node, node_id)
.ok_or_else(|| Error::ParseFailed(format!("Node {} not found", node_id)))?;
let mut cfg_builder = CfgBuilder::new(source);
cfg_builder.build_from_node(&node)?;
Ok(cfg_builder.finish())
}
fn extract_complexity(&self, node: &tree_sitter::Node<'_>) -> ComplexityMetrics {
let mut complexity = ComplexityMetrics {
cyclomatic: 1,
nesting_depth: 0,
line_count: 0,
token_count: 0,
};
calculate_complexity(node, &mut complexity, 0);
complexity
}
}
fn extract_js_imports(root: tree_sitter::Node<'_>, source: &[u8]) -> Vec<ImportInfo> {
let mut imports = Vec::new();
fn add_import(imports: &mut Vec<ImportInfo>, path: &str, alias: Option<String>) {
let path = path.trim().trim_end_matches(';').trim();
if path.is_empty() {
return;
}
imports.push(ImportInfo {
path: path.to_string(),
alias,
});
}
fn parse_import_text(imports: &mut Vec<ImportInfo>, text: &str) {
let text = text.trim().trim_end_matches(';');
if text.starts_with("import ") {
let rest = text.trim_start_matches("import ").trim();
if rest.starts_with('"') || rest.starts_with('\'') {
let module = rest.trim_matches('"').trim_matches('\'');
add_import(imports, module, None);
return;
}
let (clause, module) = if let Some((left, right)) = rest.split_once(" from ") {
(left.trim(), right.trim())
} else {
(rest, "")
};
let module = module.trim_matches('"').trim_matches('\'');
if clause.starts_with("*") {
if let Some(alias) = clause.split(" as ").nth(1) {
add_import(imports, module, Some(alias.trim().to_string()));
}
return;
}
if clause.starts_with('{') {
let inner = clause.trim_matches('{').trim_matches('}');
for part in inner.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((name, alias)) = part.split_once(" as ") {
let path = format!("{}.{}", module, name.trim());
add_import(imports, &path, Some(alias.trim().to_string()));
} else {
let path = format!("{}.{}", module, part);
add_import(
imports,
&path,
part.split('.').next_back().map(|s| s.to_string()),
);
}
}
return;
}
let mut parts = clause
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty());
if let Some(default) = parts.next() {
if !module.is_empty() {
add_import(imports, module, Some(default.to_string()));
}
}
if let Some(named) = parts.next() {
if named.starts_with('{') {
let inner = named.trim_matches('{').trim_matches('}');
for part in inner.split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((name, alias)) = part.split_once(" as ") {
let path = format!("{}.{}", module, name.trim());
add_import(imports, &path, Some(alias.trim().to_string()));
} else {
let path = format!("{}.{}", module, part);
add_import(
imports,
&path,
part.split('.').next_back().map(|s| s.to_string()),
);
}
}
}
}
}
}
fn visit(node: &tree_sitter::Node<'_>, source: &[u8], imports: &mut Vec<ImportInfo>) {
if node.kind() == "import_statement" {
if let Ok(text) = node.utf8_text(source) {
parse_import_text(imports, text);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit(&child, source, imports);
}
}
visit(&root, source, &mut imports);
imports
}
fn extract_function_signature(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
) -> Option<SignatureInfo> {
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string())
.unwrap_or_else(|| "[anonymous]".to_string());
let qualified_name = if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
};
let parameters = extract_parameters(node, source);
let is_async = node
.children(&mut node.walk())
.any(|child| child.kind() == "async");
let is_method = node.kind() == "method_definition" || !parent_path.is_empty();
let calls = extract_js_calls(node, source);
Some(SignatureInfo {
name,
qualified_name,
parameters,
return_type: None, visibility: Visibility::Public,
is_async,
is_method,
docstring: extract_docstring(node, source),
calls,
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
cyclomatic_complexity: 0,
})
}
fn extract_ts_function_signature(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
) -> Option<SignatureInfo> {
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string())
.unwrap_or_else(|| "[anonymous]".to_string());
let qualified_name = if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
};
let parameters = extract_ts_parameters(node, source);
let return_type = node
.child_by_field_name("return_type")
.and_then(|rt| rt.utf8_text(source).ok())
.map(|s| s.trim().trim_start_matches(':').trim().to_string());
let is_async = node
.children(&mut node.walk())
.any(|child| child.kind() == "async");
let is_method = node.kind() == "method_definition" || !parent_path.is_empty();
let calls = extract_js_calls(node, source);
Some(SignatureInfo {
name,
qualified_name,
parameters,
return_type,
visibility: Visibility::Public,
is_async,
is_method,
docstring: extract_docstring(node, source),
calls,
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
cyclomatic_complexity: 0,
})
}
fn extract_js_calls(node: &tree_sitter::Node<'_>, source: &[u8]) -> Vec<String> {
let mut calls = Vec::new();
fn clean_call_text(raw: &str) -> String {
raw.split('(')
.next()
.unwrap_or(raw)
.replace("?.", ".")
.trim()
.to_string()
}
fn extract_callee(node: &tree_sitter::Node<'_>, source: &[u8]) -> Option<String> {
match node.kind() {
"member_expression" | "optional_member_expression" => {
let object = node
.child_by_field_name("object")
.and_then(|n| n.utf8_text(source).ok())
.map(clean_call_text);
let property = node
.child_by_field_name("property")
.or_else(|| node.child_by_field_name("name"))
.and_then(|n| n.utf8_text(source).ok())
.map(clean_call_text);
match (object, property) {
(Some(obj), Some(prop)) => Some(format!("{}.{}", obj, prop)),
_ => node.utf8_text(source).ok().map(clean_call_text),
}
}
"call_expression" | "optional_call_expression" => node
.child_by_field_name("function")
.and_then(|n| extract_callee(&n, source)),
_ => node.utf8_text(source).ok().map(clean_call_text),
}
}
fn find_calls(node: &tree_sitter::Node<'_>, source: &[u8], calls: &mut Vec<String>) {
match node.kind() {
"call_expression" | "optional_call_expression" => {
if let Some(func) = node.child_by_field_name("function") {
if let Some(name) = extract_callee(&func, source) {
if !name.is_empty() {
calls.push(name);
}
}
}
}
"new_expression" => {
if let Some(ctor) = node.child_by_field_name("constructor") {
if let Some(name) = extract_callee(&ctor, source) {
if !name.is_empty() {
calls.push(name);
}
}
}
}
_ => {}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
find_calls(&child, source, calls);
}
}
find_calls(node, source, &mut calls);
calls
}
fn extract_parameters(node: &tree_sitter::Node<'_>, source: &[u8]) -> Vec<Parameter> {
let mut parameters = Vec::new();
if let Some(params) = node.child_by_field_name("parameters") {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "identifier" {
if let Ok(name) = child.utf8_text(source) {
parameters.push(Parameter {
name: name.to_string(),
type_annotation: None,
default_value: None,
});
}
} else if child.kind() == "rest_parameter" {
if let Some(name) = child.child_by_field_name("name") {
if let Ok(n) = name.utf8_text(source) {
parameters.push(Parameter {
name: format!("...{}", n),
type_annotation: None,
default_value: None,
});
}
}
}
}
}
parameters
}
fn extract_ts_parameters(node: &tree_sitter::Node<'_>, source: &[u8]) -> Vec<Parameter> {
let mut parameters = Vec::new();
if let Some(params) = node.child_by_field_name("parameters") {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
match child.kind() {
"identifier" => {
if let Ok(name) = child.utf8_text(source) {
parameters.push(Parameter {
name: name.to_string(),
type_annotation: None,
default_value: None,
});
}
}
"required_parameter" | "optional_parameter" => {
let param_name = child
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
let param_name = if param_name.is_none() {
let mut ccursor = child.walk();
let result = child
.children(&mut ccursor)
.find(|c| c.kind() == "identifier")
.and_then(|c| c.utf8_text(source).ok())
.map(|s| s.to_string());
result
} else {
param_name
};
if let Some(name) = param_name {
let type_annotation = child
.child_by_field_name("type")
.and_then(|t| t.utf8_text(source).ok())
.map(|s| s.trim().to_string());
parameters.push(Parameter {
name,
type_annotation,
default_value: None,
});
}
}
"rest_parameter" => {
let param_name = child
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
let param_name = if param_name.is_none() {
let mut ccursor = child.walk();
let result = child
.children(&mut ccursor)
.find(|c| c.kind() == "identifier")
.and_then(|c| c.utf8_text(source).ok())
.map(|s| s.to_string());
result
} else {
param_name
};
if let Some(name) = param_name {
let type_annotation = child
.child_by_field_name("type")
.and_then(|t| t.utf8_text(source).ok())
.map(|s| s.trim().to_string());
parameters.push(Parameter {
name: format!("...{}", name),
type_annotation,
default_value: None,
});
}
}
_ => {}
}
}
}
parameters
}
fn extract_docstring(node: &tree_sitter::Node<'_>, source: &[u8]) -> Option<String> {
let prev_sibling = node.prev_sibling();
if let Some(sibling) = prev_sibling {
if sibling.kind() == "comment" || sibling.kind() == "comment_block" {
if let Ok(text) = sibling.utf8_text(source) {
return Some(
text.trim()
.trim_start_matches("/*")
.trim_start_matches("//")
.trim_start_matches("/**")
.trim_start_matches("*")
.trim_end_matches("*/")
.trim()
.to_string(),
);
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "comment" {
if let Ok(text) = child.utf8_text(source) {
let cleaned = text
.trim()
.trim_start_matches("/**")
.trim_start_matches("/*")
.trim_start_matches("//")
.trim_end_matches("*/")
.trim();
return Some(cleaned.to_string());
}
}
}
None
}
fn find_node_by_id<'a>(
node: &'a tree_sitter::Node<'a>,
id: usize,
) -> Option<tree_sitter::Node<'a>> {
use std::collections::VecDeque;
if node.id() == id {
return Some(*node);
}
let mut queue: VecDeque<tree_sitter::Node<'a>> = VecDeque::new();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
queue.push_back(child);
}
while let Some(current) = queue.pop_front() {
if current.id() == id {
return Some(current);
}
let mut child_cursor = current.walk();
for child in current.children(&mut child_cursor) {
queue.push_back(child);
}
}
None
}
fn calculate_complexity(
node: &tree_sitter::Node<'_>,
metrics: &mut ComplexityMetrics,
depth: usize,
) {
metrics.nesting_depth = metrics.nesting_depth.max(depth);
metrics.line_count = std::cmp::max(metrics.line_count, 1);
match node.kind() {
"if_statement" | "while_statement" | "for_statement" | "for_in_statement"
| "for_of_statement" | "try_statement" | "switch_statement" | "catch_clause" => {
metrics.cyclomatic += 1;
}
"else" | "case" => {
metrics.cyclomatic += 1;
}
_ => {}
}
metrics.token_count += node.child_count();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
calculate_complexity(&child, metrics, depth + 1);
}
}
struct CfgBuilder<'a> {
source: &'a [u8],
blocks: Vec<Block>,
edges: Vec<Edge>,
next_block_id: usize,
}
impl<'a> CfgBuilder<'a> {
fn new(source: &'a [u8]) -> Self {
Self {
source,
blocks: Vec::new(),
edges: Vec::new(),
next_block_id: 0,
}
}
fn build_from_node(&mut self, node: &tree_sitter::Node<'_>) -> Result<()> {
let entry_id = self.create_block();
self.build_cfg_recursive(node, entry_id)?;
Ok(())
}
fn build_cfg_recursive(
&mut self,
node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
match node.kind() {
"if_statement" => {
self.handle_if_statement(node, current_block)?;
}
"while_statement" | "for_statement" | "for_in_statement" | "for_of_statement" => {
self.handle_loop_statement(node, current_block)?;
}
"try_statement" => {
self.handle_try_statement(node, current_block)?;
}
_ => {
if let Ok(text) = node.utf8_text(self.source) {
self.add_statement_to_block(current_block, text.to_string());
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.build_cfg_recursive(&child, current_block)?;
}
}
}
Ok(())
}
fn handle_if_statement(
&mut self,
_node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
let true_block = self.create_block();
let false_block = self.create_block();
let merge_block = self.create_block();
self.edges.push(Edge {
from: current_block,
to: true_block,
edge_type: EdgeType::TrueBranch,
});
self.edges.push(Edge {
from: current_block,
to: false_block,
edge_type: EdgeType::FalseBranch,
});
self.edges.push(Edge {
from: true_block,
to: merge_block,
edge_type: EdgeType::Unconditional,
});
self.edges.push(Edge {
from: false_block,
to: merge_block,
edge_type: EdgeType::Unconditional,
});
Ok(())
}
fn handle_loop_statement(
&mut self,
_node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
let body_block = self.create_block();
self.edges.push(Edge {
from: current_block,
to: body_block,
edge_type: EdgeType::Unconditional,
});
self.edges.push(Edge {
from: body_block,
to: current_block,
edge_type: EdgeType::Loop,
});
Ok(())
}
fn handle_try_statement(
&mut self,
_node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
let try_block = self.create_block();
let catch_block = self.create_block();
let finally_block = self.create_block();
self.edges.push(Edge {
from: current_block,
to: try_block,
edge_type: EdgeType::Unconditional,
});
self.edges.push(Edge {
from: try_block,
to: catch_block,
edge_type: EdgeType::Exception,
});
self.edges.push(Edge {
from: catch_block,
to: finally_block,
edge_type: EdgeType::Unconditional,
});
Ok(())
}
fn create_block(&mut self) -> usize {
let id = self.next_block_id;
self.next_block_id += 1;
self.blocks.push(Block {
id,
statements: Vec::new(),
});
id
}
fn add_statement_to_block(&mut self, block_id: usize, statement: String) {
if let Some(block) = self.blocks.get_mut(block_id) {
block.statements.push(statement);
}
}
fn finish(self) -> Graph<Block, Edge> {
Graph {
blocks: self.blocks,
edges: self.edges,
entry_block: 0,
exit_blocks: vec![self.next_block_id.saturating_sub(1)],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_javascript_function_extraction() {
let source = b"function greet(name) {
return 'Hello, ' + name;
}";
let parser = JavaScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert_eq!(signatures.len(), 1);
let sig = &signatures[0];
assert_eq!(sig.name, "greet");
assert_eq!(sig.parameters.len(), 1);
assert_eq!(sig.parameters[0].name, "name");
assert!(!sig.is_async);
assert!(!sig.is_method);
}
#[test]
fn test_javascript_async_function() {
let source = b"async function fetchData(url) {
return fetch(url);
}";
let parser = JavaScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert_eq!(signatures.len(), 1);
assert!(signatures[0].is_async);
assert_eq!(signatures[0].name, "fetchData");
}
#[test]
fn test_javascript_class_methods() {
let source = b"class MyClass {
method1() {
return 1;
}
async method2(x) {
return x;
}
}";
let parser = JavaScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(signatures.len() >= 2);
let methods: Vec<_> = signatures.iter().filter(|s| s.is_method).collect();
assert!(methods.len() >= 2);
}
#[test]
fn test_javascript_arrow_function() {
let source = b"const add = (a, b) => a + b;";
let parser = JavaScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(!signatures.is_empty());
let add_sig = signatures.iter().find(|s| s.name == "add");
assert!(add_sig.is_some());
if let Some(sig) = add_sig {
assert_eq!(sig.parameters.len(), 2);
}
}
#[test]
fn test_typescript_function_with_types() {
let source = b"function greet(name: string): string {
return 'Hello, ' + name;
}";
let parser = TypeScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert_eq!(signatures.len(), 1);
let sig = &signatures[0];
assert_eq!(sig.name, "greet");
assert_eq!(sig.parameters.len(), 1);
assert_eq!(sig.parameters[0].name, "name");
assert_eq!(sig.return_type, Some("string".to_string()));
}
#[test]
fn test_typescript_interface_extraction() {
let source = b"interface User {
name: string;
age: number;
}
interface Admin extends User {
permissions: string[];
}";
let parser = TypeScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(signatures.len() >= 2);
let user_interface = signatures.iter().find(|s| s.name == "User");
assert!(user_interface.is_some());
let admin_interface = signatures.iter().find(|s| s.name == "Admin");
assert!(admin_interface.is_some());
}
#[test]
fn test_typescript_type_alias() {
let source = b"type ID = string | number;
type JsonObject = Record<string, unknown>;";
let parser = TypeScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(signatures.len() >= 2);
}
#[test]
fn test_typescript_class_with_types() {
let source = b"class Calculator {
add(a: number, b: number): number {
return a + b;
}
private secret: number = 42;
}";
let parser = TypeScriptParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(!signatures.is_empty());
}
#[test]
fn test_javascript_complexity_calculation() {
let source = b"function complex(x) {
if (x > 0) {
for (let i = 0; i < x; i++) {
if (i % 2 === 0) {
console.log(i);
}
}
}
return x;
}";
let mut parser = Parser::new();
parser
.set_language(&crate::parse::traits::languages::javascript::language())
.unwrap();
let tree = parser.parse(source, None).unwrap();
let root = tree.root_node();
let js_parser = JavaScriptParser::new();
let metrics = js_parser.extract_complexity(&root);
assert!(metrics.cyclomatic > 1);
assert!(metrics.nesting_depth > 0);
}
}