UNPKG

aetherlight-analyzer

Version:

Code analysis tool to generate ÆtherLight sprint plans from any codebase

319 lines 12.2 kB
"use strict"; /** * DESIGN DECISION: Hybrid Rust parser using CLI wrapper around syn crate * WHY: TypeScript can't parse Rust natively, need Rust tooling via subprocess * * REASONING CHAIN: * 1. Rust has complex syntax (lifetimes, macros, trait bounds) * 2. Only way to parse correctly is using Rust's `syn` crate * 3. Create Rust CLI tool that outputs JSON (using syn) * 4. Call CLI from TypeScript, parse JSON output * 5. Transform to unified CodeElement types * 6. Result: Accurate Rust parsing with <3s for 30k LOC * * PATTERN: Pattern-ANALYZER-001 (AST-Based Code Analysis) * RELATED: PHASE_0_CODE_ANALYZER.md (Task A-002) * PERFORMANCE: Target <3s for 30k LOC Rust code */ 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.RustParser = void 0; const child_process_1 = require("child_process"); const path = __importStar(require("path")); const fs = __importStar(require("fs")); const types_1 = require("./types"); class RustParser { rustParserPath; constructor(rustParserPath) { /** * DESIGN DECISION: Locate Rust parser binary in project or PATH * WHY: Flexible deployment - bundled binary or system-installed */ this.rustParserPath = rustParserPath || path.join(__dirname, '../../../bin/rust-parser') || 'rust-parser'; // Fallback to PATH } /** * Parse all Rust files in directory * * DESIGN DECISION: Call Rust CLI tool via subprocess, parse JSON output * WHY: Only accurate way to parse Rust is using Rust's syn crate * * @param directoryPath - Root directory to scan * @returns ParseResult with all parsed files and dependencies */ async parse(directoryPath) { const startTime = Date.now(); // Check if Rust parser binary exists if (!this.checkRustParserExists()) { return { files: [], totalFiles: 0, totalLinesOfCode: 0, parseErrors: [ { message: `Rust parser binary not found at: ${this.rustParserPath}. Run 'npm run build:rust-parser' to compile it.`, location: { filePath: directoryPath, line: 1, column: 1 }, severity: 'error', }, ], parseDurationMs: Date.now() - startTime, }; } try { // Call Rust parser CLI const output = await this.callRustParser(directoryPath); const parsedFiles = this.transformRustOutput(output); const totalLoc = parsedFiles.reduce((sum, f) => sum + f.linesOfCode, 0); return { files: parsedFiles, totalFiles: parsedFiles.length, totalLinesOfCode: totalLoc, parseErrors: output.errors.map((msg) => ({ message: msg, location: { filePath: directoryPath, line: 1, column: 1 }, severity: 'warning', })), parseDurationMs: Date.now() - startTime, }; } catch (error) { return { files: [], totalFiles: 0, totalLinesOfCode: 0, parseErrors: [ { message: `Rust parser execution failed: ${error}`, location: { filePath: directoryPath, line: 1, column: 1 }, severity: 'error', }, ], parseDurationMs: Date.now() - startTime, }; } } /** * Check if Rust parser binary exists */ checkRustParserExists() { try { return fs.existsSync(this.rustParserPath); } catch { return false; } } /** * Call Rust parser CLI and get JSON output */ callRustParser(directoryPath) { return new Promise((resolve, reject) => { const child = (0, child_process_1.spawn)(this.rustParserPath, [directoryPath, '--json']); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { stdout += data.toString(); }); child.stderr.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code === 0) { try { const output = JSON.parse(stdout); resolve(output); } catch (error) { reject(new Error(`Failed to parse Rust parser output: ${error}`)); } } else { reject(new Error(`Rust parser failed with code ${code}: ${stderr}`)); } }); child.on('error', (error) => { reject(new Error(`Failed to spawn Rust parser: ${error}`)); }); }); } /** * Transform Rust parser output to unified CodeElement types * * REASONING CHAIN: * 1. Rust output has different structure (structs, traits, impls) * 2. Need to map to unified types (ClassElement, FunctionElement, etc.) * 3. Preserve Rust-specific info in metadata * 4. Result: Analyzers work on same types regardless of language */ transformRustOutput(output) { return output.files.map((file) => this.transformFile(file)); } transformFile(file) { const elements = []; const dependencies = []; // Transform structs file.items .filter((item) => item.kind === 'struct') .forEach((item) => { const structElement = { type: types_1.ElementType.STRUCT, name: item.name, location: this.convertLocation(file.path, item.location), documentation: item.documentation, metadata: { derives: item.attrs?.filter((a) => a.startsWith('derive')), }, fields: (item.fields || []).map((f) => this.transformField(f, file.path)), isPublic: item.visibility === 'pub', genericParams: [], // TODO: Extract from item.attrs if present }; elements.push(structElement); }); // Transform traits file.items .filter((item) => item.kind === 'trait') .forEach((item) => { const traitElement = { type: types_1.ElementType.TRAIT, name: item.name, location: this.convertLocation(file.path, item.location), documentation: item.documentation, metadata: {}, methods: (item.methods || []).map((m) => this.transformMethod(m, file.path)), isPublic: item.visibility === 'pub', }; elements.push(traitElement); }); // Transform impls file.items .filter((item) => item.kind === 'impl') .forEach((item) => { const implElement = { type: types_1.ElementType.IMPL, name: `impl ${item.impl_trait || ''} for ${item.impl_target}`.trim(), location: this.convertLocation(file.path, item.location), documentation: item.documentation, metadata: {}, targetType: item.impl_target || '', traitName: item.impl_trait, methods: (item.methods || []).map((m) => this.transformMethod(m, file.path)), }; elements.push(implElement); }); // Transform free functions file.items .filter((item) => item.kind === 'fn') .forEach((item) => { const funcElement = { type: types_1.ElementType.FUNCTION, name: item.name, location: this.convertLocation(file.path, item.location), documentation: item.documentation, metadata: {}, parameters: (item.params || []).map((p) => ({ name: p.name, type: p.type, isOptional: false, })), returnType: item.return_type, isAsync: item.attrs?.includes('async') || false, isExported: item.visibility === 'pub', }; elements.push(funcElement); }); // Transform dependencies (uses) file.uses.forEach((use_stmt) => { dependencies.push({ from: file.path, to: use_stmt.path, type: types_1.DependencyType.IMPORT, importedSymbols: use_stmt.items, }); }); return { filePath: file.path, language: 'rust', elements, dependencies, linesOfCode: file.loc, parseErrors: [], }; } transformField(field, filePath) { return { type: types_1.ElementType.PROPERTY, name: field.name, location: this.convertLocation(filePath, field.location), documentation: undefined, metadata: {}, propertyType: field.type, visibility: field.visibility === 'pub' ? 'public' : 'private', isStatic: false, isReadonly: false, // Rust doesn't have explicit readonly, handle via &references }; } transformMethod(method, filePath) { return { type: types_1.ElementType.METHOD, name: method.name, location: this.convertLocation(filePath, method.location), documentation: undefined, metadata: {}, parameters: method.params.map((p) => ({ name: p.name, type: p.type, isOptional: false, })), returnType: method.return_type, isAsync: method.is_async, isExported: method.visibility === 'pub', visibility: method.visibility === 'pub' ? 'public' : 'private', isStatic: false, // TODO: Detect from params (no &self) isAbstract: false, complexity: 1, // TODO: Calculate complexity in Rust parser }; } convertLocation(filePath, location) { return { filePath, line: location.line, column: location.column, }; } } exports.RustParser = RustParser; //# sourceMappingURL=rust-parser.js.map