| "use strict"; |
| |
| |
| |
| |
| |
| |
| |
| |
| var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| var desc = Object.getOwnPropertyDescriptor(m, k); |
| if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { |
| desc = { enumerable: true, get: function() { return m[k]; } }; |
| } |
| Object.defineProperty(o, k2, desc); |
| }) : (function(o, m, k, k2) { |
| if (k2 === undefined) k2 = k; |
| o[k2] = m[k]; |
| })); |
| var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { |
| Object.defineProperty(o, "default", { enumerable: true, value: v }); |
| }) : function(o, v) { |
| o["default"] = v; |
| }); |
| var __importStar = (this && this.__importStar) || (function () { |
| var ownKeys = function(o) { |
| ownKeys = Object.getOwnPropertyNames || function (o) { |
| var ar = []; |
| for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; |
| return ar; |
| }; |
| return ownKeys(o); |
| }; |
| return function (mod) { |
| if (mod && mod.__esModule) return mod; |
| var result = {}; |
| if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); |
| __setModuleDefault(result, mod); |
| return result; |
| }; |
| })(); |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.CodeParser = void 0; |
| exports.isTreeSitterAvailable = isTreeSitterAvailable; |
| exports.getCodeParser = getCodeParser; |
| exports.initCodeParser = initCodeParser; |
| const fs = __importStar(require("fs")); |
| const path = __importStar(require("path")); |
| |
| let Parser = null; |
| let languages = new Map(); |
| let parserError = null; |
| async function loadTreeSitter() { |
| if (Parser) |
| return true; |
| if (parserError) |
| return false; |
| try { |
| |
| Parser = require('tree-sitter'); |
| return true; |
| } |
| catch (e) { |
| parserError = new Error(`tree-sitter not installed: ${e.message}\n` + |
| `Install with: npm install tree-sitter tree-sitter-typescript tree-sitter-javascript tree-sitter-python`); |
| return false; |
| } |
| } |
| async function loadLanguage(lang) { |
| if (languages.has(lang)) |
| return languages.get(lang); |
| const langPackages = { |
| typescript: 'tree-sitter-typescript', |
| javascript: 'tree-sitter-javascript', |
| python: 'tree-sitter-python', |
| rust: 'tree-sitter-rust', |
| go: 'tree-sitter-go', |
| java: 'tree-sitter-java', |
| c: 'tree-sitter-c', |
| cpp: 'tree-sitter-cpp', |
| ruby: 'tree-sitter-ruby', |
| php: 'tree-sitter-php', |
| }; |
| const pkg = langPackages[lang]; |
| if (!pkg) |
| return null; |
| try { |
| const langModule = await Promise.resolve(`${pkg}`).then(s => __importStar(require(s))); |
| const language = langModule.default || langModule; |
| |
| if (lang === 'typescript' && language.typescript) { |
| languages.set(lang, language.typescript); |
| languages.set('tsx', language.tsx); |
| return language.typescript; |
| } |
| languages.set(lang, language); |
| return language; |
| } |
| catch { |
| return null; |
| } |
| } |
| function isTreeSitterAvailable() { |
| try { |
| require.resolve('tree-sitter'); |
| return true; |
| } |
| catch { |
| return false; |
| } |
| } |
| |
| |
| |
| class CodeParser { |
| constructor() { |
| this.parser = null; |
| this.initialized = false; |
| } |
| async init() { |
| if (this.initialized) |
| return true; |
| const loaded = await loadTreeSitter(); |
| if (!loaded) |
| return false; |
| this.parser = new Parser(); |
| this.initialized = true; |
| return true; |
| } |
| |
| |
| |
| detectLanguage(file) { |
| const ext = path.extname(file).toLowerCase(); |
| const langMap = { |
| '.ts': 'typescript', |
| '.tsx': 'tsx', |
| '.js': 'javascript', |
| '.jsx': 'javascript', |
| '.mjs': 'javascript', |
| '.cjs': 'javascript', |
| '.py': 'python', |
| '.rs': 'rust', |
| '.go': 'go', |
| '.java': 'java', |
| '.c': 'c', |
| '.h': 'c', |
| '.cpp': 'cpp', |
| '.cc': 'cpp', |
| '.cxx': 'cpp', |
| '.hpp': 'cpp', |
| '.rb': 'ruby', |
| '.php': 'php', |
| }; |
| return langMap[ext] || 'unknown'; |
| } |
| |
| |
| |
| async parse(file, content) { |
| if (!this.initialized) { |
| await this.init(); |
| } |
| if (!this.parser) |
| return null; |
| const lang = this.detectLanguage(file); |
| const language = await loadLanguage(lang); |
| if (!language) |
| return null; |
| this.parser.setLanguage(language); |
| const code = content ?? (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''); |
| const tree = this.parser.parse(code); |
| return this.convertNode(tree.rootNode); |
| } |
| convertNode(node) { |
| return { |
| type: node.type, |
| text: node.text, |
| startPosition: node.startPosition, |
| endPosition: node.endPosition, |
| children: node.children?.map((c) => this.convertNode(c)) || [], |
| }; |
| } |
| |
| |
| |
| async analyze(file, content) { |
| const start = performance.now(); |
| const lang = this.detectLanguage(file); |
| const code = content ?? (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''); |
| |
| if (this.initialized && this.parser) { |
| const language = await loadLanguage(lang); |
| if (language) { |
| this.parser.setLanguage(language); |
| const tree = this.parser.parse(code); |
| return this.analyzeTree(file, lang, tree.rootNode, code, start); |
| } |
| } |
| |
| return this.analyzeWithRegex(file, lang, code, start); |
| } |
| analyzeTree(file, lang, root, code, start) { |
| const imports = []; |
| const exports = []; |
| const functions = []; |
| const classes = []; |
| const variables = []; |
| const types = []; |
| const visit = (node) => { |
| |
| if (node.type === 'import_statement' || node.type === 'import_declaration') { |
| const imp = this.parseImport(node, lang); |
| if (imp) |
| imports.push(imp); |
| } |
| |
| if (node.type.includes('export')) { |
| const exp = this.parseExport(node, lang); |
| if (exp) |
| exports.push(exp); |
| } |
| |
| if (node.type.includes('function') || node.type === 'method_definition' || node.type === 'arrow_function') { |
| const fn = this.parseFunction(node, code, lang); |
| if (fn) |
| functions.push(fn); |
| } |
| |
| if (node.type === 'class_declaration' || node.type === 'class') { |
| const cls = this.parseClass(node, code, lang); |
| if (cls) |
| classes.push(cls); |
| } |
| |
| if (node.type === 'variable_declarator' || node.type === 'assignment') { |
| const name = this.getIdentifierName(node); |
| if (name) |
| variables.push(name); |
| } |
| |
| if (node.type === 'type_alias_declaration' || node.type === 'interface_declaration') { |
| const name = this.getIdentifierName(node); |
| if (name) |
| types.push(name); |
| } |
| |
| for (const child of node.children || []) { |
| visit(child); |
| } |
| }; |
| visit(root); |
| const lines = code.split('\n').length; |
| const complexity = this.calculateComplexity(code); |
| return { |
| file, |
| language: lang, |
| imports, |
| exports, |
| functions, |
| classes, |
| variables, |
| types, |
| complexity, |
| lines, |
| parseTime: performance.now() - start, |
| }; |
| } |
| parseImport(node, lang) { |
| try { |
| const source = this.findChild(node, 'string')?.text?.replace(/['"]/g, '') || ''; |
| const named = []; |
| let defaultImport; |
| let namespace; |
| |
| const specifiers = this.findChild(node, 'import_clause') || node; |
| for (const child of specifiers.children || []) { |
| if (child.type === 'identifier') { |
| defaultImport = child.text; |
| } |
| else if (child.type === 'namespace_import') { |
| namespace = this.getIdentifierName(child) || undefined; |
| } |
| else if (child.type === 'named_imports') { |
| for (const spec of child.children || []) { |
| if (spec.type === 'import_specifier') { |
| named.push(this.getIdentifierName(spec) || ''); |
| } |
| } |
| } |
| } |
| return { |
| source, |
| default: defaultImport, |
| named: named.filter(Boolean), |
| namespace, |
| type: 'esm', |
| }; |
| } |
| catch { |
| return null; |
| } |
| } |
| parseExport(node, lang) { |
| try { |
| if (node.type === 'export_statement') { |
| const declaration = this.findChild(node, 'declaration'); |
| if (declaration) { |
| const name = this.getIdentifierName(declaration); |
| return { name: name || 'default', type: node.text.includes('default') ? 'default' : 'named' }; |
| } |
| } |
| return null; |
| } |
| catch { |
| return null; |
| } |
| } |
| parseFunction(node, code, lang) { |
| try { |
| const name = this.getIdentifierName(node) || '<anonymous>'; |
| const params = []; |
| let returnType; |
| const isAsync = node.text.includes('async'); |
| const isExported = node.parent?.type?.includes('export'); |
| |
| const paramsNode = this.findChild(node, 'formal_parameters') || this.findChild(node, 'parameters'); |
| if (paramsNode) { |
| for (const param of paramsNode.children || []) { |
| if (param.type === 'identifier' || param.type === 'required_parameter') { |
| params.push(this.getIdentifierName(param) || ''); |
| } |
| } |
| } |
| |
| const returnNode = this.findChild(node, 'type_annotation'); |
| if (returnNode) { |
| returnType = returnNode.text.replace(/^:\s*/, ''); |
| } |
| |
| const bodyText = this.findChild(node, 'statement_block')?.text || ''; |
| const complexity = this.calculateComplexity(bodyText); |
| |
| const calls = []; |
| const callRegex = /(\w+)\s*\(/g; |
| let match; |
| while ((match = callRegex.exec(bodyText)) !== null) { |
| if (!['if', 'for', 'while', 'switch', 'catch', 'function'].includes(match[1])) { |
| calls.push(match[1]); |
| } |
| } |
| return { |
| name, |
| params: params.filter(Boolean), |
| returnType, |
| async: isAsync, |
| exported: isExported, |
| startLine: node.startPosition.row + 1, |
| endLine: node.endPosition.row + 1, |
| complexity, |
| calls: [...new Set(calls)], |
| }; |
| } |
| catch { |
| return null; |
| } |
| } |
| parseClass(node, code, lang) { |
| try { |
| const name = this.getIdentifierName(node) || '<anonymous>'; |
| let extendsClass; |
| const implementsList = []; |
| const methods = []; |
| const properties = []; |
| |
| const heritage = this.findChild(node, 'class_heritage'); |
| if (heritage) { |
| const extendsNode = this.findChild(heritage, 'extends_clause'); |
| if (extendsNode) { |
| extendsClass = this.getIdentifierName(extendsNode) || undefined; |
| } |
| } |
| |
| const body = this.findChild(node, 'class_body'); |
| if (body) { |
| for (const member of body.children || []) { |
| if (member.type === 'method_definition') { |
| const method = this.parseFunction(member, code, lang); |
| if (method) |
| methods.push(method); |
| } |
| else if (member.type === 'field_definition' || member.type === 'public_field_definition') { |
| const propName = this.getIdentifierName(member); |
| if (propName) |
| properties.push(propName); |
| } |
| } |
| } |
| return { |
| name, |
| extends: extendsClass, |
| implements: implementsList, |
| methods, |
| properties, |
| exported: node.parent?.type?.includes('export'), |
| startLine: node.startPosition.row + 1, |
| endLine: node.endPosition.row + 1, |
| }; |
| } |
| catch { |
| return null; |
| } |
| } |
| findChild(node, type) { |
| if (!node.children) |
| return null; |
| for (const child of node.children) { |
| if (child.type === type) |
| return child; |
| const found = this.findChild(child, type); |
| if (found) |
| return found; |
| } |
| return null; |
| } |
| getIdentifierName(node) { |
| if (node.type === 'identifier') |
| return node.text; |
| if (!node.children) |
| return null; |
| for (const child of node.children) { |
| if (child.type === 'identifier' || child.type === 'property_identifier') { |
| return child.text; |
| } |
| } |
| return null; |
| } |
| calculateComplexity(code) { |
| const patterns = [ |
| /\bif\b/g, |
| /\belse\b/g, |
| /\bfor\b/g, |
| /\bwhile\b/g, |
| /\bcase\b/g, |
| /\bcatch\b/g, |
| /\?\s*[^:]/g, |
| /&&/g, |
| /\|\|/g, |
| ]; |
| let complexity = 1; |
| for (const pattern of patterns) { |
| complexity += (code.match(pattern) || []).length; |
| } |
| return complexity; |
| } |
| analyzeWithRegex(file, lang, code, start) { |
| const lines = code.split('\n'); |
| const imports = []; |
| const exports = []; |
| const functions = []; |
| const classes = []; |
| const variables = []; |
| const types = []; |
| |
| const importRegex = /import\s+(?:(\w+)\s*,?\s*)?(?:\{([^}]+)\}\s*)?(?:\*\s+as\s+(\w+)\s*)?from\s+['"]([^'"]+)['"]/g; |
| const requireRegex = /(?:const|let|var)\s+(?:(\w+)|\{([^}]+)\})\s*=\s*require\s*\(['"]([^'"]+)['"]\)/g; |
| const exportRegex = /export\s+(?:(default)\s+)?(?:(class|function|const|let|var|interface|type)\s+)?(\w+)?/g; |
| const functionRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g; |
| const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g; |
| const classRegex = /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/g; |
| const typeRegex = /(?:export\s+)?(?:type|interface)\s+(\w+)/g; |
| |
| let match; |
| while ((match = importRegex.exec(code)) !== null) { |
| imports.push({ |
| source: match[4], |
| default: match[1], |
| named: match[2] ? match[2].split(',').map(s => s.trim().split(/\s+as\s+/)[0]) : [], |
| namespace: match[3], |
| type: 'esm', |
| }); |
| } |
| while ((match = requireRegex.exec(code)) !== null) { |
| imports.push({ |
| source: match[3], |
| default: match[1], |
| named: match[2] ? match[2].split(',').map(s => s.trim()) : [], |
| type: 'commonjs', |
| }); |
| } |
| |
| while ((match = exportRegex.exec(code)) !== null) { |
| if (match[3]) { |
| exports.push({ |
| name: match[3], |
| type: match[1] === 'default' ? 'default' : 'named', |
| }); |
| } |
| } |
| |
| while ((match = functionRegex.exec(code)) !== null) { |
| functions.push({ |
| name: match[1], |
| params: match[2].split(',').map(p => p.trim().split(/[:\s]/)[0]).filter(Boolean), |
| async: code.substring(match.index - 10, match.index).includes('async'), |
| exported: code.substring(match.index - 10, match.index).includes('export'), |
| startLine: code.substring(0, match.index).split('\n').length, |
| endLine: 0, |
| complexity: 1, |
| calls: [], |
| }); |
| } |
| while ((match = arrowRegex.exec(code)) !== null) { |
| functions.push({ |
| name: match[1], |
| params: [], |
| async: code.substring(match.index, match.index + 50).includes('async'), |
| exported: false, |
| startLine: code.substring(0, match.index).split('\n').length, |
| endLine: 0, |
| complexity: 1, |
| calls: [], |
| }); |
| } |
| |
| while ((match = classRegex.exec(code)) !== null) { |
| classes.push({ |
| name: match[1], |
| extends: match[2], |
| implements: [], |
| methods: [], |
| properties: [], |
| exported: code.substring(match.index - 10, match.index).includes('export'), |
| startLine: code.substring(0, match.index).split('\n').length, |
| endLine: 0, |
| }); |
| } |
| |
| while ((match = typeRegex.exec(code)) !== null) { |
| types.push(match[1]); |
| } |
| return { |
| file, |
| language: lang, |
| imports, |
| exports, |
| functions, |
| classes, |
| variables, |
| types, |
| complexity: this.calculateComplexity(code), |
| lines: lines.length, |
| parseTime: performance.now() - start, |
| }; |
| } |
| |
| |
| |
| async getSymbols(file) { |
| const analysis = await this.analyze(file); |
| return [ |
| ...analysis.functions.map(f => f.name), |
| ...analysis.classes.map(c => c.name), |
| ...analysis.types, |
| ...analysis.variables, |
| ]; |
| } |
| |
| |
| |
| async getCallGraph(file) { |
| const analysis = await this.analyze(file); |
| const graph = new Map(); |
| for (const fn of analysis.functions) { |
| graph.set(fn.name, fn.calls); |
| } |
| for (const cls of analysis.classes) { |
| for (const method of cls.methods) { |
| graph.set(`${cls.name}.${method.name}`, method.calls); |
| } |
| } |
| return graph; |
| } |
| } |
| exports.CodeParser = CodeParser; |
| |
| |
| |
| let parserInstance = null; |
| function getCodeParser() { |
| if (!parserInstance) { |
| parserInstance = new CodeParser(); |
| } |
| return parserInstance; |
| } |
| async function initCodeParser() { |
| const parser = getCodeParser(); |
| await parser.init(); |
| return parser; |
| } |
| exports.default = CodeParser; |
|
|