codeprism_mcp/tools/analysis/
javascript.rs

1//! JavaScript/TypeScript analysis tools for framework detection and code intelligence
2//!
3//! This module provides specialized tools for analyzing JavaScript and TypeScript code,
4//! including framework detection, React component analysis, and performance assessment.
5
6use crate::tools_legacy::{CallToolParams, CallToolResult, Tool, ToolContent};
7use crate::CodePrismMcpServer;
8use anyhow::Result;
9use codeprism_lang_js::JavaScriptAnalyzer;
10use serde_json::Value;
11use std::path::Path;
12
13/// List JavaScript analysis tools
14pub fn list_tools() -> Vec<Tool> {
15    vec![
16        Tool {
17            name: "analyze_javascript_frameworks".to_string(),
18            title: Some("Analyze JavaScript Frameworks".to_string()),
19            description: "Detect and analyze JavaScript/TypeScript frameworks and libraries in use"
20                .to_string(),
21            input_schema: serde_json::json!({
22                "type": "object",
23                "properties": {
24                    "include_confidence": {
25                        "type": "boolean",
26                        "description": "Include confidence scores for framework detection",
27                        "default": true
28                    },
29                    "analyze_versions": {
30                        "type": "boolean",
31                        "description": "Attempt to detect framework versions",
32                        "default": true
33                    },
34                    "frameworks": {
35                        "type": "array",
36                        "items": {
37                            "type": "string",
38                            "enum": ["react", "vue", "angular", "express", "nextjs", "nuxt", "all"]
39                        },
40                        "description": "Specific frameworks to analyze",
41                        "default": ["all"]
42                    }
43                },
44                "required": []
45            }),
46        },
47        Tool {
48            name: "analyze_react_components".to_string(),
49            title: Some("Analyze React Components".to_string()),
50            description: "Deep analysis of React components, hooks, and patterns".to_string(),
51            input_schema: serde_json::json!({
52                "type": "object",
53                "properties": {
54                    "include_hooks": {
55                        "type": "boolean",
56                        "description": "Include React hooks analysis",
57                        "default": true
58                    },
59                    "analyze_props": {
60                        "type": "boolean",
61                        "description": "Analyze component props and PropTypes",
62                        "default": true
63                    },
64                    "detect_patterns": {
65                        "type": "boolean",
66                        "description": "Detect React patterns and anti-patterns",
67                        "default": true
68                    },
69                    "include_context": {
70                        "type": "boolean",
71                        "description": "Analyze React Context usage",
72                        "default": true
73                    }
74                },
75                "required": []
76            }),
77        },
78        Tool {
79            name: "analyze_nodejs_patterns".to_string(),
80            title: Some("Analyze Node.js Patterns".to_string()),
81            description:
82                "Analyze Node.js backend patterns, database integrations, and architecture"
83                    .to_string(),
84            input_schema: serde_json::json!({
85                "type": "object",
86                "properties": {
87                    "include_databases": {
88                        "type": "boolean",
89                        "description": "Include database integration analysis",
90                        "default": true
91                    },
92                    "analyze_routing": {
93                        "type": "boolean",
94                        "description": "Analyze routing patterns and middleware",
95                        "default": true
96                    },
97                    "detect_orms": {
98                        "type": "boolean",
99                        "description": "Detect ORM frameworks and patterns",
100                        "default": true
101                    },
102                    "include_security": {
103                        "type": "boolean",
104                        "description": "Include security pattern analysis",
105                        "default": true
106                    }
107                },
108                "required": []
109            }),
110        },
111    ]
112}
113
114/// Route JavaScript analysis tool calls
115pub async fn call_tool(
116    server: &CodePrismMcpServer,
117    params: &CallToolParams,
118) -> Result<CallToolResult> {
119    match params.name.as_str() {
120        "analyze_javascript_frameworks" => {
121            analyze_javascript_frameworks(server, params.arguments.as_ref()).await
122        }
123        "analyze_react_components" => {
124            analyze_react_components(server, params.arguments.as_ref()).await
125        }
126        "analyze_nodejs_patterns" => {
127            analyze_nodejs_patterns(server, params.arguments.as_ref()).await
128        }
129        _ => Err(anyhow::anyhow!(
130            "Unknown JavaScript analysis tool: {}",
131            params.name
132        )),
133    }
134}
135
136/// Analyze JavaScript/TypeScript frameworks
137async fn analyze_javascript_frameworks(
138    server: &CodePrismMcpServer,
139    arguments: Option<&Value>,
140) -> Result<CallToolResult> {
141    let default_args = serde_json::json!({});
142    let args = arguments.unwrap_or(&default_args);
143
144    let include_confidence = args
145        .get("include_confidence")
146        .and_then(|v| v.as_bool())
147        .unwrap_or(true);
148
149    let analyze_versions = args
150        .get("analyze_versions")
151        .and_then(|v| v.as_bool())
152        .unwrap_or(true);
153
154    let frameworks = args
155        .get("frameworks")
156        .and_then(|v| v.as_array())
157        .map(|arr| {
158            arr.iter()
159                .filter_map(|v| v.as_str())
160                .map(|s| s.to_string())
161                .collect::<Vec<_>>()
162        })
163        .unwrap_or_else(|| vec!["all".to_string()]);
164
165    let analyzer = JavaScriptAnalyzer::new();
166    let mut all_frameworks = Vec::new();
167    let mut files_analyzed = 0;
168
169    // Get repository path and analyze files
170    if let Some(repo_path) = server.repository_path() {
171        match server.scanner().discover_files(repo_path) {
172            Ok(files) => {
173                for file_path in files {
174                    if is_javascript_file(&file_path) {
175                        match std::fs::read_to_string(&file_path) {
176                            Ok(content) => {
177                                files_analyzed += 1;
178
179                                // Analyze content using our Phase 1.3 capabilities
180                                match analyzer.detect_frameworks(&content) {
181                                    Ok(frameworks_detected) => {
182                                        // Extract framework information
183                                        for framework in frameworks_detected {
184                                            if frameworks.contains(&"all".to_string())
185                                                || frameworks
186                                                    .contains(&framework.name.to_lowercase())
187                                            {
188                                                all_frameworks.push(framework);
189                                            }
190                                        }
191                                    }
192                                    Err(e) => {
193                                        eprintln!("Error analyzing {}: {}", file_path.display(), e);
194                                    }
195                                }
196                            }
197                            Err(e) => {
198                                eprintln!("Error reading {}: {}", file_path.display(), e);
199                            }
200                        }
201                    }
202                }
203            }
204            Err(e) => {
205                return Err(anyhow::anyhow!("Failed to discover files: {}", e));
206            }
207        }
208    }
209
210    // Aggregate and analyze results
211    let mut framework_summary = std::collections::HashMap::new();
212    for framework in &all_frameworks {
213        let entry = framework_summary
214            .entry(framework.name.clone())
215            .or_insert_with(|| {
216                serde_json::json!({
217                    "name": framework.name,
218                    "confidence": framework.confidence,
219                    "files_count": 0,
220                    "versions": Vec::<String>::new(),
221                    "features": Vec::<String>::new(),
222                    "best_practices": Vec::<String>::new()
223                })
224            });
225
226        // Update counts and features
227        entry["files_count"] = serde_json::json!(entry["files_count"].as_u64().unwrap_or(0) + 1);
228
229        if include_confidence {
230            let current_confidence = entry["confidence"].as_f64().unwrap_or(0.0);
231            let new_confidence = (current_confidence + framework.confidence as f64) / 2.0;
232            entry["confidence"] = serde_json::json!(new_confidence);
233        }
234
235        // Merge features and best practices
236        for feature in &framework.features_used {
237            if !entry["features"]
238                .as_array()
239                .unwrap()
240                .iter()
241                .any(|f| f.as_str() == Some(feature))
242            {
243                entry["features"]
244                    .as_array_mut()
245                    .unwrap()
246                    .push(serde_json::json!(feature));
247            }
248        }
249
250        for practice in &framework.best_practices {
251            if !entry["best_practices"]
252                .as_array()
253                .unwrap()
254                .iter()
255                .any(|p| p.as_str() == Some(practice))
256            {
257                entry["best_practices"]
258                    .as_array_mut()
259                    .unwrap()
260                    .push(serde_json::json!(practice));
261            }
262        }
263    }
264
265    let result = serde_json::json!({
266        "analysis_summary": {
267            "files_analyzed": files_analyzed,
268            "frameworks_detected": framework_summary.len(),
269            "total_framework_instances": all_frameworks.len()
270        },
271        "frameworks": framework_summary.values().collect::<Vec<_>>(),
272        "analysis_metadata": {
273            "include_confidence": include_confidence,
274            "analyze_versions": analyze_versions,
275            "requested_frameworks": frameworks,
276            "timestamp": chrono::Utc::now().to_rfc3339(),
277            "analyzer_version": "2.1.0"
278        }
279    });
280
281    Ok(CallToolResult {
282        content: vec![ToolContent::Text {
283            text: serde_json::to_string_pretty(&result)?,
284        }],
285        is_error: Some(false),
286    })
287}
288
289/// Analyze React components
290async fn analyze_react_components(
291    server: &CodePrismMcpServer,
292    arguments: Option<&Value>,
293) -> Result<CallToolResult> {
294    let default_args = serde_json::json!({});
295    let args = arguments.unwrap_or(&default_args);
296
297    let include_hooks = args
298        .get("include_hooks")
299        .and_then(|v| v.as_bool())
300        .unwrap_or(true);
301
302    let analyze_props = args
303        .get("analyze_props")
304        .and_then(|v| v.as_bool())
305        .unwrap_or(true);
306
307    let _detect_patterns = args
308        .get("detect_patterns")
309        .and_then(|v| v.as_bool())
310        .unwrap_or(true);
311
312    let include_context = args
313        .get("include_context")
314        .and_then(|v| v.as_bool())
315        .unwrap_or(true);
316
317    let analyzer = JavaScriptAnalyzer::new();
318    let mut all_components = Vec::new();
319    let mut files_analyzed = 0;
320
321    // Get repository path and analyze React files
322    if let Some(repo_path) = server.repository_path() {
323        match server.scanner().discover_files(repo_path) {
324            Ok(files) => {
325                for file_path in files {
326                    if is_react_file(&file_path) {
327                        match std::fs::read_to_string(&file_path) {
328                            Ok(content) => {
329                                files_analyzed += 1;
330
331                                // Analyze content using our Phase 1.3 capabilities
332                                match analyzer.analyze_react_patterns(&content) {
333                                    Ok(components) => {
334                                        // Extract React component information
335                                        for component in components {
336                                            let hooks_data = if include_hooks {
337                                                component
338                                                    .hooks_used
339                                                    .iter()
340                                                    .map(|h| {
341                                                        serde_json::json!({
342                                                            "name": h.name,
343                                                            "hook_type": h.hook_type,
344                                                            "dependencies": h.dependencies,
345                                                            "custom_hook": h.custom_hook
346                                                        })
347                                                    })
348                                                    .collect::<Vec<_>>()
349                                            } else {
350                                                Vec::new()
351                                            };
352
353                                            let props_data = if analyze_props {
354                                                Some(serde_json::json!({
355                                                    "prop_names": component.props_analysis.prop_names,
356                                                    "has_prop_types": component.props_analysis.has_prop_types,
357                                                    "has_default_props": component.props_analysis.has_default_props,
358                                                    "destructured": component.props_analysis.destructured,
359                                                    "typescript_props": component.props_analysis.typescript_props
360                                                }))
361                                            } else {
362                                                None
363                                            };
364
365                                            let context_data = if include_context {
366                                                component
367                                                    .context_usage
368                                                    .iter()
369                                                    .map(|c| {
370                                                        serde_json::json!({
371                                                            "context_name": c.context_name,
372                                                            "usage_type": c.usage_type,
373                                                            "values_consumed": c.values_consumed
374                                                        })
375                                                    })
376                                                    .collect::<Vec<_>>()
377                                            } else {
378                                                Vec::new()
379                                            };
380
381                                            all_components.push(serde_json::json!({
382                                                "name": component.name,
383                                                "type": format!("{:?}", component.component_type),
384                                                "file": file_path.display().to_string(),
385                                                "hooks": if include_hooks { Some(hooks_data) } else { None },
386                                                "props": props_data,
387                                                "jsx_elements": component.jsx_elements,
388                                                "lifecycle_methods": component.lifecycle_methods,
389                                                "context_usage": if include_context { Some(context_data) } else { None }
390                                            }));
391                                        }
392                                    }
393                                    Err(e) => {
394                                        eprintln!("Error analyzing {}: {}", file_path.display(), e);
395                                    }
396                                }
397                            }
398                            Err(e) => {
399                                eprintln!("Error reading {}: {}", file_path.display(), e);
400                            }
401                        }
402                    }
403                }
404            }
405            Err(e) => {
406                return Err(anyhow::anyhow!("Failed to discover files: {}", e));
407            }
408        }
409    }
410
411    // Generate component analysis summary
412    let hooks_summary = if include_hooks {
413        let mut hook_counts = std::collections::HashMap::new();
414        for component in &all_components {
415            if let Some(hooks) = component.get("hooks").and_then(|h| h.as_array()) {
416                for hook in hooks {
417                    if let Some(hook_name) = hook.as_str() {
418                        *hook_counts.entry(hook_name.to_string()).or_insert(0) += 1;
419                    }
420                }
421            }
422        }
423        Some(hook_counts)
424    } else {
425        None
426    };
427
428    let result = serde_json::json!({
429        "analysis_summary": {
430            "files_analyzed": files_analyzed,
431            "components_found": all_components.len(),
432            "hooks_analysis_included": include_hooks,
433            "props_analysis_included": analyze_props,
434            "context_analysis_included": include_context
435        },
436        "components": all_components,
437        "hooks_summary": hooks_summary,
438        "analysis_metadata": {
439            "timestamp": chrono::Utc::now().to_rfc3339(),
440            "analyzer_version": "2.1.0"
441        }
442    });
443
444    Ok(CallToolResult {
445        content: vec![ToolContent::Text {
446            text: serde_json::to_string_pretty(&result)?,
447        }],
448        is_error: Some(false),
449    })
450}
451
452/// Analyze Node.js patterns
453async fn analyze_nodejs_patterns(
454    server: &CodePrismMcpServer,
455    arguments: Option<&Value>,
456) -> Result<CallToolResult> {
457    let default_args = serde_json::json!({});
458    let args = arguments.unwrap_or(&default_args);
459
460    let include_databases = args
461        .get("include_databases")
462        .and_then(|v| v.as_bool())
463        .unwrap_or(true);
464
465    let analyze_routing = args
466        .get("analyze_routing")
467        .and_then(|v| v.as_bool())
468        .unwrap_or(true);
469
470    let detect_orms = args
471        .get("detect_orms")
472        .and_then(|v| v.as_bool())
473        .unwrap_or(true);
474
475    let include_security = args
476        .get("include_security")
477        .and_then(|v| v.as_bool())
478        .unwrap_or(true);
479
480    let analyzer = JavaScriptAnalyzer::new();
481    let mut nodejs_patterns = Vec::new();
482    let mut files_analyzed = 0;
483
484    // Get repository path and analyze Node.js files
485    if let Some(repo_path) = server.repository_path() {
486        match server.scanner().discover_files(repo_path) {
487            Ok(files) => {
488                for file_path in files {
489                    if is_nodejs_file(&file_path) {
490                        match std::fs::read_to_string(&file_path) {
491                            Ok(content) => {
492                                files_analyzed += 1;
493
494                                // Analyze content using our Phase 1.3 capabilities
495                                match analyzer.analyze_nodejs_patterns(&content) {
496                                    Ok(patterns) => {
497                                        // Extract Node.js pattern information
498                                        for pattern in patterns {
499                                            let route_data = pattern.route_info.map(|r| {
500                                                serde_json::json!({
501                                                    "path": r.path,
502                                                    "method": r.method,
503                                                    "parameters": r.parameters,
504                                                    "query_params": r.query_params,
505                                                    "middleware_used": r.middleware_used
506                                                })
507                                            });
508
509                                            let db_patterns_data = if include_databases {
510                                                pattern
511                                                    .database_patterns
512                                                    .iter()
513                                                    .map(|db| {
514                                                        serde_json::json!({
515                                                            "db_type": db.db_type,
516                                                            "operations": db.operations,
517                                                            "orm_framework": db.orm_framework
518                                                        })
519                                                    })
520                                                    .collect::<Vec<_>>()
521                                            } else {
522                                                Vec::new()
523                                            };
524
525                                            nodejs_patterns.push(serde_json::json!({
526                                                "pattern_type": format!("{:?}", pattern.pattern_type),
527                                                "file": file_path.display().to_string(),
528                                                "framework": pattern.framework,
529                                                "route_info": route_data,
530                                                "middleware_chain": pattern.middleware_chain,
531                                                "http_methods": pattern.http_methods,
532                                                "database_patterns": if include_databases { Some(db_patterns_data) } else { None }
533                                            }));
534                                        }
535                                    }
536                                    Err(e) => {
537                                        eprintln!("Error analyzing {}: {}", file_path.display(), e);
538                                    }
539                                }
540                            }
541                            Err(e) => {
542                                eprintln!("Error reading {}: {}", file_path.display(), e);
543                            }
544                        }
545                    }
546                }
547            }
548            Err(e) => {
549                return Err(anyhow::anyhow!("Failed to discover files: {}", e));
550            }
551        }
552    }
553
554    let result = serde_json::json!({
555        "analysis_summary": {
556            "files_analyzed": files_analyzed,
557            "patterns_found": nodejs_patterns.len(),
558            "database_analysis_included": include_databases,
559            "routing_analysis_included": analyze_routing,
560            "orm_analysis_included": detect_orms,
561            "security_analysis_included": include_security
562        },
563        "nodejs_patterns": nodejs_patterns,
564        "analysis_metadata": {
565            "timestamp": chrono::Utc::now().to_rfc3339(),
566            "analyzer_version": "2.1.0"
567        }
568    });
569
570    Ok(CallToolResult {
571        content: vec![ToolContent::Text {
572            text: serde_json::to_string_pretty(&result)?,
573        }],
574        is_error: Some(false),
575    })
576}
577
578/// Helper function to determine if a file is a JavaScript/TypeScript file
579fn is_javascript_file(path: &Path) -> bool {
580    if let Some(extension) = path.extension() {
581        matches!(
582            extension.to_str(),
583            Some("js") | Some("jsx") | Some("ts") | Some("tsx") | Some("mjs") | Some("cjs")
584        )
585    } else {
586        false
587    }
588}
589
590/// Helper function to determine if a file is a React file
591fn is_react_file(path: &Path) -> bool {
592    if let Some(extension) = path.extension() {
593        matches!(extension.to_str(), Some("jsx") | Some("tsx"))
594    } else if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
595        name.contains("component") || name.contains("Component") || name.starts_with("use")
596    } else {
597        false
598    }
599}
600
601/// Helper function to determine if a file is a Node.js file
602fn is_nodejs_file(path: &Path) -> bool {
603    if let Some(extension) = path.extension() {
604        matches!(
605            extension.to_str(),
606            Some("js") | Some("ts") | Some("mjs") | Some("cjs")
607        )
608    } else if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
609        matches!(
610            name,
611            "server.js"
612                | "app.js"
613                | "index.js"
614                | "main.js"
615                | "api.js"
616                | "routes.js"
617                | "middleware.js"
618        )
619    } else {
620        false
621    }
622}