import json import tree_sitter from tree_sitter import Language, Parser import tree_sitter_python as tspython import tree_sitter_javascript as tsjavascript import tree_sitter_typescript as tstypescript import tree_sitter_html as tshtml from pathlib import Path from typing import Dict, List, Any, Optional class CodeAnalyzer: """Анализирует код с помощью tree-sitter и извлекает структуру.""" LANGUAGES = { '.py': Language(tspython.language()), '.js': Language(tsjavascript.language()), '.ts': Language(tstypescript.language_typescript()), '.tsx': Language(tstypescript.language_tsx()), '.html': Language(tshtml.language()), } def __init__(self): self.parsers = {} for ext, language in self.LANGUAGES.items(): # Язык теперь передаётся сразу при создании парсера self.parsers[ext] = Parser(language) def analyze_file(self, content: str, extension: str) -> Dict[str, Any]: """Анализирует содержимое файла и возвращает структурированную информацию.""" parser = self.parsers.get(extension) if not parser: # Для неподдерживаемых расширений возвращаем сырой текст (обрезанный до разумного) return {"raw": content[:1000] + "..." if len(content) > 1000 else content} tree = parser.parse(bytes(content, 'utf-8')) root = tree.root_node info = { "imports": [], "exports": [], "functions": [], "classes": [], "variables": [], "has_side_effects": False, "jsx_elements": [] if extension in ('.tsx', '.jsx') else None } # Обходим все узлы self._traverse(root, info, extension) return info def _traverse(self, node, info, extension): """Рекурсивно обходит AST и заполняет info.""" if node.type == 'function_definition' or node.type == 'function_declaration': func_name = self._get_node_text(node.child_by_field_name('name')) if func_name: params = [] params_node = node.child_by_field_name('parameters') if params_node: for p in params_node.children: if p.type == 'identifier' or p.type == 'formal_parameter': params.append(self._get_node_text(p)) info['functions'].append({ "name": func_name, "params": params }) elif node.type == 'class_definition' or node.type == 'class_declaration': class_name = self._get_node_text(node.child_by_field_name('name')) if class_name: methods = [] body = node.child_by_field_name('body') if body: for child in body.children: if child.type == 'method_definition' or child.type == 'function_definition': mname = self._get_node_text(child.child_by_field_name('name')) if mname: methods.append(mname) info['classes'].append({ "name": class_name, "methods": methods }) elif node.type == 'import_statement' or node.type == 'import_from_statement': # Для Python и JS/TS импорты выглядят по-разному, но мы упростим import_text = self._get_node_text(node) info['imports'].append(import_text.strip()) elif node.type == 'export_statement' or node.type == 'export_named_statement': export_text = self._get_node_text(node) info['exports'].append(export_text.strip()) elif node.type == 'assignment' or node.type == 'variable_declaration': # Попытка извлечь имена переменных верхнего уровня var_names = [] if node.type == 'assignment': left = node.child_by_field_name('left') if left and left.type == 'identifier': var_names.append(self._get_node_text(left)) elif node.type == 'variable_declaration': for child in node.children: if child.type == 'variable_declarator': name_node = child.child_by_field_name('name') if name_node: var_names.append(self._get_node_text(name_node)) info['variables'].extend(var_names) # Побочные эффекты: выражение верхнего уровня, не являющееся импортом/экспортом/декларацией if node.type == 'expression_statement': # Исключаем вызовы, которые являются частью модуля (например, вызов ReactDOM.render) # Считаем, что любое выражение верхнего уровня — потенциальный side-effect # Можно уточнить позже info['has_side_effects'] = True if extension in ('.tsx', '.jsx') and node.type == 'jsx_element': # Извлекаем название компонента opening = node.child_by_field_name('opening_element') if opening: name_node = opening.child_by_field_name('name') if name_node: info['jsx_elements'].append(self._get_node_text(name_node)) # Рекурсивно обходим детей for child in node.children: self._traverse(child, info, extension) @staticmethod def _get_node_text(node) -> str: if node is None: return "" return node.text.decode('utf-8')