Files
ragmcp/tools/parse_ts.js
T
2025-11-04 00:23:51 +00:00

177 lines
4.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Usage: node parse_ts.js file.ts
const fs = require('fs');
const path = require('path');
const { parse } = require('@typescript-eslint/typescript-estree');
function getLoc(node) {
if (node.loc) {
return { start: node.loc.start.line, end: node.loc.end.line };
}
return { start: 1, end: 1 };
}
function extractLeadingComment(node, sourceCode) {
if (!node.range || !sourceCode) return "";
const startIdx = node.range[0];
let commentEnd = startIdx;
// Look backward for comments
let i = startIdx - 1;
let commentLines = [];
let inBlock = false;
while (i >= 0) {
const char = sourceCode[i];
if (char === '\n') break;
i--;
}
const lineStart = i + 1;
const lineAbove = sourceCode.slice(lineStart, startIdx).trim();
// Check for // comment on same line before node
if (lineAbove.startsWith('//')) {
return lineAbove.substring(2).trim();
}
// Look further up for multi-line or JSDoc
const lines = sourceCode.substring(0, lineStart).split('\n');
for (let j = lines.length - 1; j >= Math.max(0, lines.length - 5); j--) {
const line = lines[j].trim();
if (line.startsWith('//')) {
commentLines.unshift(line.substring(2).trim());
} else if (line.endsWith('*/')) {
inBlock = true;
commentLines.unshift(line.slice(0, -2).trim());
} else if (inBlock) {
if (line.startsWith('/*') || line.startsWith('/**')) {
commentLines.unshift(line.slice(2).trim());
break;
} else {
commentLines.unshift(line);
}
} else if (line === '') {
if (commentLines.length > 0) continue;
else break;
} else {
break;
}
}
return commentLines.join('\n').trim();
}
function extractDeclarations(ast, sourceCode) {
const decls = [];
function visit(node) {
if (!node || typeof node !== 'object') return;
if (Array.isArray(node)) {
node.forEach(n => visit(n));
return;
}
let docComment = extractLeadingComment(node, sourceCode);
if (node.type === 'FunctionDeclaration' && node.id?.name) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'function',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
else if (
node.type === 'VariableDeclarator' &&
node.id?.type === 'Identifier' &&
node.init?.type === 'ArrowFunctionExpression'
) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'function',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
else if (node.type === 'ClassDeclaration' && node.id?.name) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'class',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
else if (node.type === 'TSInterfaceDeclaration' && node.id?.name) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'interface',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
else if (node.type === 'TSTypeAliasDeclaration' && node.id?.name) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'type',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
else if (
node.type === 'VariableDeclarator' &&
node.id?.type === 'Identifier'
) {
const parent = node.parent;
if (
parent?.type === 'VariableDeclaration' &&
['const', 'let'].includes(parent.kind)
) {
const loc = getLoc(node);
decls.push({
name: node.id.name,
type: 'variable',
doc_comment: docComment,
start_line: loc.start,
end_line: loc.end
});
}
}
Object.values(node).forEach(visit);
}
visit(ast);
return decls;
}
if (process.argv.length < 3) {
console.error('Usage: node parse_ts.js <file.ts>');
process.exit(1);
}
const filePath = path.resolve(process.argv[2]);
try {
const code = fs.readFileSync(filePath, 'utf8');
const ast = parse(code, {
sourceType: 'module',
loc: true,
range: true,
comment: false // we extract manually for simplicity
});
const decls = extractDeclarations(ast, code);
console.log(JSON.stringify(decls, null, 2));
} catch (e) {
console.error(`ERR: ${e.message}`);
process.exit(2);
}