context_creator/core/semantic/languages/
javascript.rs1use 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 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(module_path)?;
77
78 if self.is_external_module(module_path) {
80 return Ok(ResolvedPath {
81 path: base_dir.join("package.json"), is_external: true,
83 confidence: 1.0,
84 });
85 }
86
87 if module_path.starts_with('.') {
89 if let Some(parent) = from_file.parent() {
90 let clean_path = if let Some(stripped) = module_path.strip_prefix("./") {
92 stripped } else {
94 module_path };
96 let resolved_path = parent.join(clean_path);
97
98 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 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 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 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 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 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 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 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}