context_creator/core/semantic/languages/
javascript.rs

1//! Semantic analyzer for JavaScript
2
3use crate::core::semantic::{
4    analyzer::{AnalysisResult, LanguageAnalyzer, SemanticContext, SemanticResult},
5    path_validator::{validate_import_path, validate_module_name},
6    query_engine::QueryEngine,
7    resolver::{ModuleResolver, ResolvedPath},
8};
9use crate::utils::error::ContextCreatorError;
10use std::path::Path;
11use tree_sitter::Parser;
12
13#[allow(clippy::new_without_default)]
14pub struct JavaScriptAnalyzer {
15    query_engine: QueryEngine,
16}
17
18impl JavaScriptAnalyzer {
19    pub fn new() -> Self {
20        let language = tree_sitter_javascript::language();
21        let query_engine = QueryEngine::new(language, "javascript")
22            .expect("Failed to create JavaScript query engine");
23        Self { query_engine }
24    }
25}
26
27impl LanguageAnalyzer for JavaScriptAnalyzer {
28    fn language_name(&self) -> &'static str {
29        "JavaScript"
30    }
31
32    fn analyze_file(
33        &self,
34        path: &Path,
35        content: &str,
36        context: &SemanticContext,
37    ) -> SemanticResult<AnalysisResult> {
38        let mut parser = Parser::new();
39        parser
40            .set_language(tree_sitter_javascript::language())
41            .map_err(|e| ContextCreatorError::ParseError(format!("Failed to set language: {e}")))?;
42
43        let mut result = self
44            .query_engine
45            .analyze_with_parser(&mut parser, content)?;
46
47        // Resolve type definitions for the type references found
48        self.query_engine.resolve_type_definitions(
49            &mut result.type_references,
50            path,
51            &context.base_dir,
52        )?;
53
54        Ok(result)
55    }
56
57    fn can_handle_extension(&self, extension: &str) -> bool {
58        extension == "js" || extension == "jsx"
59    }
60
61    fn supported_extensions(&self) -> Vec<&'static str> {
62        vec!["js", "jsx"]
63    }
64}
65
66pub struct JavaScriptModuleResolver;
67
68impl ModuleResolver for JavaScriptModuleResolver {
69    fn resolve_import(
70        &self,
71        module_path: &str,
72        from_file: &Path,
73        base_dir: &Path,
74    ) -> Result<ResolvedPath, ContextCreatorError> {
75        // Validate module name for security
76        validate_module_name(module_path)?;
77
78        // Handle Node.js built-in modules
79        if self.is_external_module(module_path) {
80            return Ok(ResolvedPath {
81                path: base_dir.join("package.json"), // Point to package.json as indicator
82                is_external: true,
83                confidence: 1.0,
84            });
85        }
86
87        // Handle relative imports (./, ../)
88        if module_path.starts_with('.') {
89            if let Some(parent) = from_file.parent() {
90                // Properly resolve relative paths by removing leading "./"
91                let clean_path = if let Some(stripped) = module_path.strip_prefix("./") {
92                    stripped // Remove "./" prefix
93                } else {
94                    module_path // Keep as-is for "../" or other relative paths
95                };
96                let resolved_path = parent.join(clean_path);
97
98                // Try different extensions
99                for ext in &["js", "jsx", "ts", "tsx"] {
100                    let with_ext = resolved_path.with_extension(ext);
101                    if with_ext.exists() {
102                        let validated_path = validate_import_path(base_dir, &with_ext)?;
103                        return Ok(ResolvedPath {
104                            path: validated_path,
105                            is_external: false,
106                            confidence: 0.9,
107                        });
108                    }
109                }
110
111                // Try as directory with index file
112                for ext in &["js", "jsx", "ts", "tsx"] {
113                    let index_path = resolved_path.join(format!("index.{ext}"));
114                    if index_path.exists() {
115                        let validated_path = validate_import_path(base_dir, &index_path)?;
116                        return Ok(ResolvedPath {
117                            path: validated_path,
118                            is_external: false,
119                            confidence: 0.9,
120                        });
121                    }
122                }
123            }
124        }
125
126        // Handle absolute imports from node_modules or project root
127        let search_paths = vec![
128            base_dir.to_path_buf(),
129            from_file.parent().unwrap_or(base_dir).to_path_buf(),
130        ];
131
132        for search_path in &search_paths {
133            // Try as a file
134            for ext in &["js", "jsx", "ts", "tsx"] {
135                let file_path = search_path.join(format!("{module_path}.{ext}"));
136                if file_path.exists() {
137                    let validated_path = validate_import_path(base_dir, &file_path)?;
138                    return Ok(ResolvedPath {
139                        path: validated_path,
140                        is_external: false,
141                        confidence: 0.8,
142                    });
143                }
144            }
145
146            // Try as a directory with index file
147            for ext in &["js", "jsx", "ts", "tsx"] {
148                let index_path = search_path.join(module_path).join(format!("index.{ext}"));
149                if index_path.exists() {
150                    let validated_path = validate_import_path(base_dir, &index_path)?;
151                    return Ok(ResolvedPath {
152                        path: validated_path,
153                        is_external: false,
154                        confidence: 0.8,
155                    });
156                }
157            }
158        }
159
160        // Otherwise, assume it's an external package
161        Ok(ResolvedPath {
162            path: base_dir.join("package.json"),
163            is_external: true,
164            confidence: 0.5,
165        })
166    }
167
168    fn get_file_extensions(&self) -> Vec<&'static str> {
169        vec!["js", "jsx"]
170    }
171
172    fn is_external_module(&self, module_path: &str) -> bool {
173        // Node.js built-in modules
174        let builtin_modules = [
175            "assert",
176            "buffer",
177            "child_process",
178            "cluster",
179            "crypto",
180            "dgram",
181            "dns",
182            "domain",
183            "events",
184            "fs",
185            "http",
186            "https",
187            "net",
188            "os",
189            "path",
190            "punycode",
191            "querystring",
192            "readline",
193            "repl",
194            "stream",
195            "string_decoder",
196            "tls",
197            "tty",
198            "url",
199            "util",
200            "v8",
201            "vm",
202            "zlib",
203            "process",
204            "console",
205            "timers",
206            "module",
207        ];
208
209        // Common npm packages
210        let common_packages = [
211            "react",
212            "react-dom",
213            "vue",
214            "angular",
215            "lodash",
216            "express",
217            "next",
218            "webpack",
219            "babel",
220            "eslint",
221            "typescript",
222            "jest",
223            "mocha",
224            "chai",
225            "sinon",
226            "axios",
227            "moment",
228            "dayjs",
229            "socket.io",
230            "cors",
231            "helmet",
232            "bcrypt",
233            "jsonwebtoken",
234            "passport",
235            "multer",
236            "nodemailer",
237            "mongoose",
238            "sequelize",
239            "prisma",
240            "graphql",
241            "apollo",
242            "redux",
243            "mobx",
244            "zustand",
245            "styled-components",
246            "emotion",
247            "tailwindcss",
248        ];
249
250        let first_part = module_path.split('/').next().unwrap_or("");
251        builtin_modules.contains(&first_part) || common_packages.contains(&first_part)
252    }
253}