#!/usr/bin/env node // Enhanced TypeScript/JavaScript AST parser // 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]; // Find the start of the line containing the node let lineStart = startIdx; while (lineStart > 0 && sourceCode[lineStart - 1] !== "\n") { lineStart--; } // Check for inline comment on the same line const lineContent = sourceCode.slice(lineStart, startIdx).trim(); if (lineContent.startsWith("//")) { return lineContent.substring(2).trim(); } // Look backward through previous lines for comments const lines = sourceCode.substring(0, lineStart).split("\n"); const commentLines = []; let inBlockComment = false; for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) { const line = lines[i].trim(); if (line.endsWith("*/")) { // End of block comment found inBlockComment = true; const blockContent = line .slice(0, -2) .replace(/^\*+\s*/, "") .trim(); if (blockContent) { commentLines.unshift(blockContent); } } else if (inBlockComment) { if (line.startsWith("/*") || line.startsWith("/**")) { // Start of block comment found const blockContent = line .substring(line.indexOf("/*") + 2) .replace(/^\*+\s*/, "") .trim(); if (blockContent) { commentLines.unshift(blockContent); } break; } else { // Inside block comment const blockContent = line.replace(/^\*+\s*/, "").trim(); if (blockContent) { commentLines.unshift(blockContent); } } } else if (line.startsWith("//")) { // Single-line comment commentLines.unshift(line.substring(2).trim()); } else if (line === "") { // Empty line - continue if we already have comments if (commentLines.length === 0) { break; } } else { // Non-comment, non-empty line - stop looking break; } } return commentLines.join("\n").trim(); } function extractTypeInfo(node) { const typeInfo = {}; // Extract type annotation if (node.typeAnnotation) { typeInfo.returnType = extractTypeString(node.typeAnnotation.typeAnnotation); } // Extract parameters for functions if (node.params) { typeInfo.parameters = node.params.map((param) => { const paramInfo = { name: extractParamName(param), type: param.typeAnnotation ? extractTypeString(param.typeAnnotation.typeAnnotation) : "any", }; if (param.optional) { paramInfo.optional = true; } return paramInfo; }); } // Extract generics if (node.typeParameters) { typeInfo.generics = node.typeParameters.params.map((tp) => tp.name.name); } return typeInfo; } function extractParamName(param) { if (param.type === "Identifier") { return param.name; } if (param.type === "RestElement" && param.argument.type === "Identifier") { return "..." + param.argument.name; } if (param.type === "AssignmentPattern" && param.left.type === "Identifier") { return param.left.name; } return "unknown"; } function extractTypeString(typeNode) { if (!typeNode) return "any"; switch (typeNode.type) { case "TSStringKeyword": return "string"; case "TSNumberKeyword": return "number"; case "TSBooleanKeyword": return "boolean"; case "TSAnyKeyword": return "any"; case "TSVoidKeyword": return "void"; case "TSUndefinedKeyword": return "undefined"; case "TSNullKeyword": return "null"; case "TSTypeReference": if (typeNode.typeName.type === "Identifier") { let typeStr = typeNode.typeName.name; if (typeNode.typeParameters) { const params = typeNode.typeParameters.params .map(extractTypeString) .join(", "); typeStr += `<${params}>`; } return typeStr; } return "unknown"; case "TSArrayType": return extractTypeString(typeNode.elementType) + "[]"; case "TSUnionType": return typeNode.types.map(extractTypeString).join(" | "); case "TSIntersectionType": return typeNode.types.map(extractTypeString).join(" & "); case "TSFunctionType": return "Function"; case "TSTypeLiteral": return "object"; default: return "unknown"; } } function extractDeclarations(ast, sourceCode) { const decls = []; const parentMap = new WeakMap(); function setParents(node, parent = null) { if (!node || typeof node !== "object") return; if (Array.isArray(node)) { node.forEach((n) => setParents(n, parent)); return; } parentMap.set(node, parent); Object.values(node).forEach((child) => setParents(child, node)); } setParents(ast); function visit(node) { if (!node || typeof node !== "object") return; if (Array.isArray(node)) { node.forEach((n) => visit(n)); return; } const docComment = extractLeadingComment(node, sourceCode); const loc = getLoc(node); // Function Declarations if (node.type === "FunctionDeclaration" && node.id?.name) { const typeInfo = extractTypeInfo(node); decls.push({ name: node.id.name, type: "function", doc_comment: docComment, start_line: loc.start, end_line: loc.end, is_async: node.async || false, is_generator: node.generator || false, ...typeInfo, }); } // Arrow Functions assigned to variables else if ( node.type === "VariableDeclarator" && node.id?.type === "Identifier" && node.init?.type === "ArrowFunctionExpression" ) { const typeInfo = extractTypeInfo(node.init); decls.push({ name: node.id.name, type: "function", doc_comment: docComment, start_line: loc.start, end_line: loc.end, is_async: node.init.async || false, is_arrow: true, ...typeInfo, }); } // Class Declarations else if (node.type === "ClassDeclaration" && node.id?.name) { const classInfo = { name: node.id.name, type: "class", doc_comment: docComment, start_line: loc.start, end_line: loc.end, is_abstract: node.abstract || false, }; // Extract extends if (node.superClass) { if (node.superClass.type === "Identifier") { classInfo.extends = node.superClass.name; } } // Extract implements if (node.implements && node.implements.length > 0) { classInfo.implements = node.implements.map( (impl) => impl.expression.name || "unknown", ); } // Extract generics if (node.typeParameters) { classInfo.generics = node.typeParameters.params.map( (tp) => tp.name.name, ); } // Extract methods and properties const methods = []; const properties = []; if (node.body && node.body.body) { for (const member of node.body.body) { if (member.type === "MethodDefinition" && member.key?.name) { methods.push(member.key.name); } else if (member.type === "PropertyDefinition" && member.key?.name) { properties.push(member.key.name); } } } if (methods.length > 0) { classInfo.methods = methods; } if (properties.length > 0) { classInfo.properties = properties; } decls.push(classInfo); } // Interface Declarations else if (node.type === "TSInterfaceDeclaration" && node.id?.name) { const interfaceInfo = { name: node.id.name, type: "interface", doc_comment: docComment, start_line: loc.start, end_line: loc.end, }; // Extract extends if (node.extends && node.extends.length > 0) { interfaceInfo.extends = node.extends.map( (ext) => ext.expression.name || "unknown", ); } // Extract generics if (node.typeParameters) { interfaceInfo.generics = node.typeParameters.params.map( (tp) => tp.name.name, ); } // Extract properties/methods const properties = []; const methods = []; if (node.body && node.body.body) { for (const member of node.body.body) { if (member.type === "TSPropertySignature" && member.key?.name) { properties.push(member.key.name); } else if (member.type === "TSMethodSignature" && member.key?.name) { methods.push(member.key.name); } } } if (properties.length > 0) { interfaceInfo.properties = properties; } if (methods.length > 0) { interfaceInfo.methods = methods; } decls.push(interfaceInfo); } // Type Alias Declarations else if (node.type === "TSTypeAliasDeclaration" && node.id?.name) { const typeInfo = { name: node.id.name, type: "type", doc_comment: docComment, start_line: loc.start, end_line: loc.end, }; // Extract generics if (node.typeParameters) { typeInfo.generics = node.typeParameters.params.map( (tp) => tp.name.name, ); } // Extract type definition if (node.typeAnnotation) { typeInfo.type_definition = extractTypeString(node.typeAnnotation); } decls.push(typeInfo); } // Enum Declarations else if (node.type === "TSEnumDeclaration" && node.id?.name) { const members = []; if (node.members) { for (const member of node.members) { if (member.id?.type === "Identifier") { members.push(member.id.name); } } } decls.push({ name: node.id.name, type: "enum", doc_comment: docComment, start_line: loc.start, end_line: loc.end, members: members, }); } // Variable Declarations (const, let, var) else if ( node.type === "VariableDeclarator" && node.id?.type === "Identifier" ) { const parent = parentMap.get(node); if (parent?.type === "VariableDeclaration") { const varInfo = { name: node.id.name, type: "variable", doc_comment: docComment, start_line: loc.start, end_line: loc.end, var_kind: parent.kind, // const, let, or var }; // Extract type annotation if (node.id.typeAnnotation) { varInfo.var_type = extractTypeString( node.id.typeAnnotation.typeAnnotation, ); } decls.push(varInfo); } } // Import Declarations else if (node.type === "ImportDeclaration" && node.source?.value) { const importInfo = { name: node.source.value, type: "import", start_line: loc.start, end_line: loc.end, import_path: node.source.value, }; // Extract imported names const imports = []; if (node.specifiers) { for (const spec of node.specifiers) { if (spec.type === "ImportSpecifier" && spec.imported?.name) { imports.push(spec.imported.name); } else if ( spec.type === "ImportDefaultSpecifier" && spec.local?.name ) { imports.push(`default as ${spec.local.name}`); } else if ( spec.type === "ImportNamespaceSpecifier" && spec.local?.name ) { imports.push(`* as ${spec.local.name}`); } } } if (imports.length > 0) { importInfo.imported_names = imports; } decls.push(importInfo); } // Export Declarations else if (node.type === "ExportNamedDeclaration") { if (node.declaration) { // Export with declaration: export const x = ... visit(node.declaration); } else if (node.specifiers && node.specifiers.length > 0) { // Named exports: export { x, y } for (const spec of node.specifiers) { if (spec.exported?.name) { decls.push({ name: spec.exported.name, type: "export", start_line: loc.start, end_line: loc.end, export_kind: "named", }); } } } } // Continue traversing Object.values(node).forEach(visit); } visit(ast); return decls; } // Main execution if (process.argv.length < 3) { console.error("Usage: node parse_ts.js "); 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, ecmaFeatures: { jsx: true, }, }); const decls = extractDeclarations(ast, code); console.log(JSON.stringify(decls, null, 2)); } catch (e) { console.error(`ERR: ${e.message}`); process.exit(2); }