import fs from 'fs';
import path from 'path';
export interface DebugBlock {
startLine: number;
endLine: number;
content: string;
}
export class CodeParser {
/**
* Find all debug blocks (between // debug-start and // debug-end)
* Supports multiple languages: JavaScript, Java, Objective-C, Python, PHP
*/
findDebugBlocks(filePath: string): DebugBlock[] {
const content = this.readFileContent(filePath);
if (!content) {
return [];
}
const ext = path.extname(filePath).toLowerCase();
const lines = content.split('\n');
const blocks: DebugBlock[] = [];
let currentBlock: Partial<DebugBlock> | null = null;
// Determine comment markers based on file extension
const startMarker = this.getDebugStartMarker(ext);
const endMarker = this.getDebugEndMarker(ext);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (trimmed === startMarker) {
currentBlock = {
startLine: i + 1, // 1-indexed line numbers
content: ''
};
} else if (trimmed === endMarker && currentBlock) {
blocks.push({
startLine: currentBlock.startLine!,
endLine: i + 1,
content: (currentBlock.content || '').trim()
});
currentBlock = null;
} else if (currentBlock) {
currentBlock.content += line + '\n';
}
}
return blocks;
}
/**
* Get debug start marker based on file extension
*/
private getDebugStartMarker(ext: string): string {
switch (ext) {
case '.py':
return '# debug-start';
case '.java':
case '.kt':
case '.m':
case '.mm':
case '.h':
case '.js':
case '.ts':
case '.jsx':
case '.tsx':
case '.php':
default:
return '// debug-start';
}
}
/**
* Get debug end marker based on file extension
*/
private getDebugEndMarker(ext: string): string {
switch (ext) {
case '.py':
return '# debug-end';
case '.java':
case '.kt':
case '.m':
case '.mm':
case '.h':
case '.js':
case '.ts':
case '.jsx':
case '.tsx':
case '.php':
default:
return '// debug-end';
}
}
/**
* Remove all debug blocks from file
* Also removes temporary debug markers (TEMPORARY DEBUG MARKER comments)
*/
removeDebugBlocks(filePath: string): { success: boolean; removedCount: number; linesRemoved: number[] } {
const content = this.readFileContent(filePath);
if (!content) {
return { success: false, removedCount: 0, linesRemoved: [] };
}
const ext = path.extname(filePath).toLowerCase();
const lines = content.split('\n');
const linesToRemove: number[] = [];
let inDebugBlock = false;
let inTempMarker = false;
// Determine comment markers based on file extension
const startMarker = this.getDebugStartMarker(ext);
const endMarker = this.getDebugEndMarker(ext);
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trim();
const line = lines[i];
// Check for debug block markers
if (trimmed === startMarker) {
inDebugBlock = true;
linesToRemove.push(i);
} else if (trimmed === endMarker) {
inDebugBlock = false;
linesToRemove.push(i);
} else if (inDebugBlock) {
linesToRemove.push(i);
}
// Check for temporary debug markers (comment-based)
else if (line.includes('TEMPORARY DEBUG MARKER') ||
line.includes('TEMPORARY DEBUG MARKER - WILL BE REVERTED')) {
inTempMarker = true;
linesToRemove.push(i);
} else if (inTempMarker && (line.includes('END TEMPORARY DEBUG MARKER') ||
trimmed.includes('END TEMPORARY DEBUG MARKER'))) {
inTempMarker = false;
linesToRemove.push(i);
} else if (inTempMarker) {
linesToRemove.push(i);
}
}
if (linesToRemove.length === 0) {
return { success: true, removedCount: 0, linesRemoved: [] };
}
// Filter out lines to remove
const newLines = lines.filter((_, index) => !linesToRemove.includes(index));
const newContent = newLines.join('\n');
try {
fs.writeFileSync(filePath, newContent, 'utf8');
return {
success: true,
removedCount: linesToRemove.filter((line, idx, arr) => arr.indexOf(line) === idx).length,
linesRemoved: linesToRemove.map(l => l + 1) // Convert to 1-indexed
};
} catch (error) {
return {
success: false,
removedCount: 0,
linesRemoved: []
};
}
}
/**
* Insert debug code at specific line
*/
insertDebugCode(
filePath: string,
debugCode: string,
insertLine: number
): { success: boolean; modifiedLines: number[] } {
const content = this.readFileContent(filePath);
if (!content) {
return { success: false, modifiedLines: [] };
}
const lines = content.split('\n');
// Adjust insertLine to be 0-indexed
const insertIndex = Math.min(Math.max(0, insertLine - 1), lines.length);
// Insert debug code
const debugLines = debugCode.split('\n');
lines.splice(insertIndex, 0, ...debugLines);
const newContent = lines.join('\n');
try {
fs.writeFileSync(filePath, newContent, 'utf8');
return {
success: true,
modifiedLines: Array.from(
{ length: debugLines.length },
(_, i) => insertIndex + i + 1
)
};
} catch (error) {
return { success: false, modifiedLines: [] };
}
}
/**
* Count debug blocks in file
*/
countDebugBlocks(filePath: string): number {
return this.findDebugBlocks(filePath).length;
}
/**
* Check if file has any debug blocks
*/
hasDebugBlocks(filePath: string): boolean {
return this.countDebugBlocks(filePath) > 0;
}
/**
* Extract function/class context around a line
*/
extractContext(filePath: string, lineNumber: number, contextLines: number = 5): {
before: string[];
at: string;
after: string[];
} {
const content = this.readFileContent(filePath);
if (!content) {
return { before: [], at: '', after: [] };
}
const lines = content.split('\n');
const index = Math.max(0, Math.min(lineNumber - 1, lines.length - 1));
return {
before: lines.slice(Math.max(0, index - contextLines), index),
at: lines[index] || '',
after: lines.slice(index + 1, Math.min(lines.length, index + contextLines + 1))
};
}
/**
* Find all files in directory that contain debug blocks
*/
findFilesWithDebugBlocks(dirPath: string, fileExtensions: string[] = ['.js', '.ts', '.jsx', '.tsx', '.py', '.php', '.java', '.kt', '.m', '.mm', '.h']): string[] {
const filesWithBlocks: string[] = [];
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and other common directories
if (!['node_modules', '.git', 'dist', 'build', '.next'].includes(entry.name)) {
filesWithBlocks.push(...this.findFilesWithDebugBlocks(fullPath, fileExtensions));
}
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (fileExtensions.includes(ext)) {
if (this.hasDebugBlocks(fullPath)) {
filesWithBlocks.push(fullPath);
}
}
}
}
} catch (error) {
// Directory read error, skip
}
return filesWithBlocks;
}
private readFileContent(filePath: string): string | null {
try {
return fs.readFileSync(filePath, 'utf8');
} catch {
return null;
}
}
}