import Parser from 'tree-sitter';
import { CodeElement, SupportedLanguage } from '../types/index.js';
export abstract class BaseParser {
protected parser: Parser;
protected language: SupportedLanguage;
constructor(language: SupportedLanguage) {
this.parser = new Parser();
this.language = language;
}
abstract setLanguage(): void;
abstract extractElements(tree: Parser.Tree, sourceCode: string, filePath: string): CodeElement[];
abstract hasDocumentation(node: Parser.SyntaxNode, sourceCode: string): { has: boolean; doc?: string };
parse(sourceCode: string, filePath: string): CodeElement[] {
try {
this.setLanguage();
const tree = this.parser.parse(sourceCode);
return this.extractElements(tree, sourceCode, filePath);
} catch (error) {
throw new Error(`Failed to parse ${filePath}: ${(error as Error).message}`);
}
}
protected nodeToString(node: Parser.SyntaxNode, sourceCode: string): string {
return sourceCode.slice(node.startIndex, node.endIndex);
}
protected findPreviousSibling(node: Parser.SyntaxNode): Parser.SyntaxNode | null {
if (!node.parent) return null;
const siblings = node.parent.children;
const index = siblings.indexOf(node);
if (index > 0) {
return siblings[index - 1];
}
return null;
}
protected extractDocComment(node: Parser.SyntaxNode, sourceCode: string): string | undefined {
const prev = this.findPreviousSibling(node);
if (!prev) return undefined;
const docPatterns = ['comment', 'block_comment', 'line_comment', 'string'];
if (docPatterns.includes(prev.type)) {
const text = this.nodeToString(prev, sourceCode);
return this.cleanDocComment(text);
}
return undefined;
}
protected cleanDocComment(text: string): string {
return text
.replace(/^\/\*\*|\*\/$/g, '')
.replace(/^\s*\*\s?/gm, '')
.replace(/^\/\/\s?/gm, '')
.replace(/^#\s?/gm, '')
.trim();
}
}