@paulohenriquevn/m2js
Version:
Transform TypeScript/JavaScript code into LLM-friendly Markdown summaries + Smart Dead Code Detection + Graph-Deep Diff Analysis. Extract exported functions, classes, and JSDoc comments for better AI context with 60%+ token reduction. Intelligent dead cod
589 lines • 22 kB
JavaScript
;
/* eslint-disable max-lines */
/* eslint-disable max-lines-per-function */
/* eslint-disable max-depth */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateMarkdown = generateMarkdown;
exports.getOutputPath = getOutputPath;
exports.generateDependencyMarkdown = generateDependencyMarkdown;
const path_1 = __importDefault(require("path"));
const DEFAULT_OPTIONS = {
includeComments: false,
};
function generateMarkdown(parsedFile,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_options = DEFAULT_OPTIONS) {
// Note: _options parameter reserved for future includeComments feature
const sections = [];
sections.push(generateEnhancedHeader(parsedFile));
sections.push(generateExportsSection(parsedFile));
if (parsedFile.functions.length > 0) {
sections.push(generateFunctionsSection(parsedFile.functions));
}
if (parsedFile.classes.length > 0) {
sections.push(generateClassesSection(parsedFile.classes));
}
if (parsedFile.functions.length === 0 && parsedFile.classes.length === 0) {
sections.push('No exported functions or classes found.');
}
return sections.join('\n\n');
}
function generateEnhancedHeader(parsedFile) {
// Try to show relative path from current working directory, fallback to absolute
const cwd = process.cwd();
let displayPath = parsedFile.filePath;
if (parsedFile.filePath.startsWith(cwd)) {
displayPath = path_1.default.relative(cwd, parsedFile.filePath);
// Add ./ prefix for relative paths to be clear
if (!displayPath.startsWith('.')) {
displayPath = `./${displayPath}`;
}
}
return `# ${escapeMarkdown(displayPath)}`;
}
function generateExportsSection(parsedFile) {
const { exportMetadata } = parsedFile;
const sections = ['## Exports'];
// Add export counts
if (exportMetadata.totalFunctions > 0) {
const plural = exportMetadata.totalFunctions === 1 ? '' : 's';
sections.push(`- **Functions**: ${exportMetadata.totalFunctions} exported function${plural}`);
}
if (exportMetadata.totalClasses > 0) {
const plural = exportMetadata.totalClasses === 1 ? '' : 'es';
sections.push(`- **Classes**: ${exportMetadata.totalClasses} exported class${plural}`);
}
// Add default export information
if (exportMetadata.hasDefaultExport) {
const exportType = exportMetadata.defaultExportType;
const exportName = exportMetadata.defaultExportName;
sections.push(`- **Default Export**: ${exportName} ${exportType}`);
}
// If no exports found
if (exportMetadata.totalFunctions === 0 &&
exportMetadata.totalClasses === 0) {
sections.push('- No exported functions or classes found');
}
return sections.join('\n');
}
function generateFunctionsSection(functions) {
const sections = ['## Functions'];
// Use compact format for utility files with many simple functions
if (shouldUseCompactFormat(functions)) {
sections.push(generateCompactFunctionsTable(functions));
}
else {
functions.forEach(func => {
sections.push(generateFunctionMarkdown(func));
});
}
return sections.join('\n\n');
}
/**
* Determines if we should use compact format for functions
* Criteria: 5+ functions where most are simple utilities
*/
function shouldUseCompactFormat(functions) {
if (functions.length < 5)
return false;
// Check if most functions are simple (few params, simple types)
const simpleFunctions = functions.filter(func => {
const hasSimpleParams = func.params.length <= 3;
const hasSimpleReturn = func.returnType &&
['boolean', 'string', 'number', 'void'].includes(func.returnType);
const hasShortJSDoc = !func.jsDoc || func.jsDoc.length < 200;
return hasSimpleParams && hasSimpleReturn && hasShortJSDoc;
});
// Use compact format if 70%+ of functions are simple
return simpleFunctions.length / functions.length >= 0.7;
}
/**
* Generates a compact table format for utility functions
*/
function generateCompactFunctionsTable(functions) {
const sections = [];
sections.push('| Function | Parameters | Returns | Description |');
sections.push('|----------|------------|---------|-------------|');
functions.forEach(func => {
const name = func.isDefault ? 'default' : func.name;
const params = func.params
.map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type || 'any'}`)
.join(', ');
const returnType = func.returnType || 'unknown';
// Extract first line of JSDoc as description
let description = 'Utility function';
if (func.jsDoc) {
// Try multiple patterns to extract description
const patterns = [
/\/\*\*\s*\n?\s*\*\s*(.+?)(?:\n|\*\/)/, // Standard JSDoc
/\/\*\*\s*(.+?)(?:\n|\*\/)/, // Inline JSDoc
/\*\s*(.+?)(?:\n|\*\/|@)/, // Any comment line before @
];
for (const pattern of patterns) {
const match = func.jsDoc.match(pattern);
if (match && match[1] && match[1].trim()) {
description = match[1].trim();
break;
}
}
// Clean up common prefixes and shorten
description = description.replace(/^(Validates?|Checks?|Returns?|Gets?|Creates?)\s+/i, '');
if (description.length > 45) {
description = description.substring(0, 42) + '...';
}
}
sections.push(`| \`${name}\` | \`${params}\` | \`${returnType}\` | ${description} |`);
});
// Add a note about compact format
sections.push('');
sections.push('*Compact format used for utility functions - see source for implementation details*');
return sections.join('\n');
}
function generateFunctionMarkdown(func) {
const sections = [];
const displayName = func.isDefault ? 'default' : func.name;
sections.push(`### ${escapeMarkdown(displayName)}`);
if (func.jsDoc) {
sections.push(func.jsDoc);
sections.push('');
}
// Add hierarchical parameters section
const paramsList = generateParametersList(func.params);
const returnType = generateReturnType(func.returnType);
// Add empty line after function name if we have params or return type
if (paramsList || returnType) {
sections.push('');
}
if (paramsList) {
sections.push(paramsList);
sections.push('');
}
if (returnType) {
sections.push(returnType);
sections.push('');
}
sections.push('```typescript');
sections.push(func.signature);
sections.push('```');
return sections.join('\n');
}
function escapeMarkdown(text) {
return text
.replace(/\\/g, '\\\\')
.replace(/\*/g, '\\*')
.replace(/_/g, '\\_')
.replace(/`/g, '\\`')
.replace(/#/g, '\\#')
.replace(/\[/g, '\\[')
.replace(/\]/g, '\\]')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)');
}
function generateClassesSection(classes) {
const sections = ['## Classes'];
classes.forEach(cls => {
sections.push(generateClassMarkdown(cls));
});
return sections.join('\n\n');
}
function generateClassMarkdown(cls) {
const sections = [];
sections.push(`### ${escapeMarkdown(cls.name)}`);
if (cls.jsDoc) {
sections.push(cls.jsDoc);
sections.push('');
}
// Add hierarchical methods overview
const methodsList = generateMethodsList(cls.methods);
if (methodsList) {
sections.push(methodsList);
sections.push('');
}
// Generate class signature with public methods only
const publicMethods = cls.methods.filter(method => !method.isPrivate);
sections.push('```typescript');
if (publicMethods.length > 0) {
sections.push(`export class ${cls.name} {`);
publicMethods.forEach(method => {
sections.push(` ${method.signature}`);
});
sections.push('}');
}
else {
sections.push(`export class ${cls.name} {}`);
}
sections.push('```');
// Only generate individual method docs for classes with few methods
// For utility classes, the class signature is sufficient
if (publicMethods.length <= 4) {
publicMethods.forEach(method => {
sections.push('');
sections.push(generateMethodMarkdown(method));
});
}
else {
sections.push('');
sections.push('*Method details available in source code*');
}
return sections.join('\n');
}
function generateMethodMarkdown(method) {
const sections = [];
sections.push(`#### ${escapeMarkdown(method.name)}`);
if (method.jsDoc) {
sections.push(method.jsDoc);
sections.push('');
}
// Add hierarchical parameters section
const paramsList = generateParametersList(method.params);
const returnType = generateReturnType(method.returnType);
// Add empty line after method name if we have params or return type
if (paramsList || returnType) {
sections.push('');
}
if (paramsList) {
sections.push(paramsList);
sections.push('');
}
if (returnType) {
sections.push(returnType);
sections.push('');
}
sections.push('```typescript');
sections.push(method.signature);
sections.push('```');
return sections.join('\n');
}
function generateParametersList(params) {
if (params.length === 0) {
return '';
}
const sections = ['**Parameters:**'];
params.forEach(param => {
const optional = param.optional ? '?' : '';
const type = param.type || 'unknown';
sections.push(`- ${param.name}${optional}: ${type}`);
});
return sections.join('\n');
}
function generateReturnType(returnType) {
if (!returnType) {
return '';
}
return `**Returns:** ${returnType}`;
}
function generateMethodsList(methods) {
const publicMethods = methods.filter(method => !method.isPrivate);
if (publicMethods.length === 0) {
return '';
}
const sections = ['**Methods:**'];
publicMethods.forEach(method => {
sections.push(`- ${method.name}`);
});
return sections.join('\n');
}
function getOutputPath(inputPath, customOutput) {
if (customOutput) {
return customOutput;
}
const parsedPath = path_1.default.parse(inputPath);
return path_1.default.join(parsedPath.dir, `${parsedPath.name}.md`);
}
// === DEPENDENCY GRAPH MARKDOWN GENERATION ===
/**
* Generate comprehensive dependency graph markdown
*/
function generateDependencyMarkdown(graph, options = {}) {
const sections = [];
// Header with project info
sections.push(generateGraphHeader(graph));
// Module dependencies
sections.push(generateModuleDependencies(graph));
// Metrics section
sections.push(generateGraphMetrics(graph));
// Circular dependencies warning
if (graph.metrics.circularDependencies.length > 0) {
sections.push(generateCircularDependencies(graph.metrics.circularDependencies));
}
// Architecture layers
sections.push(generateArchitectureLayers(graph));
// Mermaid diagram if requested
if (options.includeMermaid) {
sections.push(generateMermaidDiagram(graph));
}
return sections.join('\n\n');
}
/**
* Generate graph header
*/
function generateGraphHeader(graph) {
const cwd = process.cwd();
let displayPath = graph.projectPath;
if (graph.projectPath.startsWith(cwd)) {
displayPath = path_1.default.relative(cwd, graph.projectPath);
if (!displayPath.startsWith('.')) {
displayPath = `./${displayPath}`;
}
}
return `# Dependency Analysis - ${escapeMarkdown(displayPath)}`;
}
/**
* Generate module dependencies section
*/
function generateModuleDependencies(graph) {
const sections = ['## 🔗 Module Dependencies'];
// Group dependencies by source file
const dependencyMap = new Map();
graph.edges.forEach(edge => {
if (!dependencyMap.has(edge.from)) {
dependencyMap.set(edge.from, []);
}
dependencyMap.get(edge.from).push(edge);
});
// Sort files and generate dependency lists
const sortedFiles = Array.from(dependencyMap.keys()).sort();
if (sortedFiles.length === 0) {
sections.push('*No dependencies found*');
return sections.join('\n');
}
// Group by internal vs external
const internalFiles = sortedFiles.filter(file => dependencyMap.get(file).some(dep => !dep.isExternal));
// Internal dependencies
if (internalFiles.length > 0) {
sections.push('### Internal Dependencies');
internalFiles.forEach(file => {
const deps = dependencyMap.get(file).filter(dep => !dep.isExternal);
if (deps.length > 0) {
const fileName = path_1.default.basename(file);
// Group dependencies by target file and count occurrences
const depCounts = new Map();
deps.forEach(dep => {
const count = depCounts.get(dep.to) || 0;
depCounts.set(dep.to, count + 1);
});
// Format with counts for duplicates
const depList = Array.from(depCounts.entries())
.map(([depPath, count]) => {
return count > 1 ? `\`${depPath}\` (${count}x)` : `\`${depPath}\``;
})
.join(', ');
sections.push(`- **${fileName}** → ${depList}`);
}
});
sections.push('');
}
// External dependencies
const allExternalDeps = new Set();
graph.edges
.filter(edge => edge.isExternal)
.forEach(edge => {
allExternalDeps.add(edge.to);
});
if (allExternalDeps.size > 0) {
sections.push('### External Dependencies');
const sortedExternal = Array.from(allExternalDeps).sort();
sortedExternal.forEach(dep => {
const usedBy = graph.edges
.filter(edge => edge.to === dep && edge.isExternal)
.map(edge => path_1.default.basename(edge.from));
const uniqueUsers = Array.from(new Set(usedBy)).sort();
sections.push(`- **${dep}** (used by: ${uniqueUsers.join(', ')})`);
});
}
return sections.join('\n');
}
/**
* Generate metrics section
*/
function generateGraphMetrics(graph) {
const { metrics } = graph;
const sections = ['## Dependency Metrics'];
sections.push(`- **Total Modules**: ${metrics.totalNodes}`);
sections.push(`- **Total Dependencies**: ${metrics.totalEdges}`);
sections.push(`- **Internal Dependencies**: ${metrics.internalDependencies}`);
sections.push(`- **External Dependencies**: ${metrics.externalDependencies}`);
sections.push(`- **Average Dependencies per Module**: ${metrics.averageDependencies}`);
if (metrics.mostConnectedModule) {
const moduleName = path_1.default.basename(metrics.mostConnectedModule);
sections.push(`- **Most Connected Module**: ${moduleName}`);
}
// Circular dependencies status
if (metrics.circularDependencies.length === 0) {
sections.push('- **Circular Dependencies**: None detected');
}
else {
sections.push(`- **Circular Dependencies**: ${metrics.circularDependencies.length} detected`);
}
return sections.join('\n');
}
/**
* Generate circular dependencies warning
*/
function generateCircularDependencies(cycles) {
const sections = ['## Circular Dependencies'];
cycles.forEach((cycle, index) => {
const cycleDisplay = cycle.map(file => path_1.default.basename(file)).join(' → ');
sections.push(`${index + 1}. **${cycleDisplay}**`);
});
sections.push('');
sections.push('*Circular dependencies can lead to runtime errors and should be resolved.*');
return sections.join('\n');
}
/**
* Generate architecture layers
*/
function generateArchitectureLayers(graph) {
const sections = ['## Architecture Layers'];
// Simple layer detection based on common patterns
const layers = detectArchitectureLayers(graph);
Object.entries(layers).forEach(([layerName, files]) => {
if (files.length > 0) {
sections.push(`### ${layerName}`);
files.forEach(file => {
const baseName = path_1.default.basename(file);
const description = getFileDescription(baseName);
sections.push(`- **${baseName}** - ${description}`);
});
sections.push('');
}
});
return sections.join('\n');
}
/**
* Detect architecture layers based on file patterns
*/
function detectArchitectureLayers(graph) {
const layers = {
'CLI Layer': [],
'Core Logic': [],
Utilities: [],
Types: [],
Tests: [],
};
graph.nodes.forEach(file => {
const baseName = path_1.default.basename(file).toLowerCase();
if (baseName.includes('cli') || baseName.includes('command')) {
layers['CLI Layer'].push(file);
}
else if (baseName.includes('test') || baseName.includes('spec')) {
layers['Tests'].push(file);
}
else if (baseName.includes('type') || baseName.includes('interface')) {
layers['Types'].push(file);
}
else if (baseName.includes('util') || baseName.includes('helper')) {
layers['Utilities'].push(file);
}
else {
layers['Core Logic'].push(file);
}
});
// Remove empty layers
Object.keys(layers).forEach(key => {
if (layers[key].length === 0) {
delete layers[key];
}
});
return layers;
}
/**
* Get file description based on name patterns
*/
function getFileDescription(fileName) {
const name = fileName.toLowerCase();
if (name.includes('cli'))
return 'Command-line interface and argument parsing';
if (name.includes('parser'))
return 'Code parsing and AST analysis';
if (name.includes('generator'))
return 'Markdown generation and formatting';
if (name.includes('analyzer'))
return 'Dependency analysis and graph building';
if (name.includes('scanner'))
return 'File system scanning and discovery';
if (name.includes('batch'))
return 'Batch processing coordination';
if (name.includes('types'))
return 'TypeScript type definitions';
if (name.includes('util'))
return 'Utility functions and helpers';
if (name.includes('test'))
return 'Test cases and validation';
return 'Core functionality';
}
/**
* Generate Mermaid diagram
*/
function generateMermaidDiagram(graph) {
const sections = ['## Visual Dependency Map'];
sections.push('```mermaid');
sections.push('graph TD');
// Create node definitions with clean names
const nodeMap = new Map();
graph.nodes.forEach((file, index) => {
const cleanName = path_1.default
.basename(file, path_1.default.extname(file))
.replace(/[^a-zA-Z0-9]/g, '')
.substring(0, 10);
const nodeId = `${cleanName}${index}`;
nodeMap.set(file, nodeId);
const displayName = path_1.default.basename(file);
sections.push(` ${nodeId}[${displayName}]`);
});
// Add edges (only internal dependencies for clarity)
const internalEdges = graph.edges.filter(edge => !edge.isExternal);
// Deduplicate edges by grouping from -> to relationships
const edgeMap = new Map();
internalEdges.forEach(edge => {
const fromNode = nodeMap.get(edge.from);
// Resolve relative paths to absolute paths for mapping
let resolvedToPath = edge.to;
if (edge.to.startsWith('./') || edge.to.startsWith('../')) {
try {
resolvedToPath = path_1.default.resolve(path_1.default.dirname(edge.from), edge.to);
// Add common file extensions if missing
if (!path_1.default.extname(resolvedToPath)) {
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
for (const ext of extensions) {
const withExt = resolvedToPath + ext;
if (nodeMap.has(withExt)) {
resolvedToPath = withExt;
break;
}
}
}
}
catch {
// If resolution fails, try to find by basename
const basename = path_1.default.basename(edge.to);
for (const [nodePath] of nodeMap.entries()) {
if (path_1.default.basename(nodePath, path_1.default.extname(nodePath)) ===
basename.replace(/\.(ts|tsx|js|jsx)$/, '')) {
resolvedToPath = nodePath;
break;
}
}
}
}
const toNode = nodeMap.get(resolvedToPath);
if (fromNode && toNode && fromNode !== toNode) {
if (!edgeMap.has(fromNode)) {
edgeMap.set(fromNode, new Set());
}
edgeMap.get(fromNode).add(toNode);
}
});
// Generate unique edges
for (const [fromNode, toNodes] of edgeMap.entries()) {
for (const toNode of toNodes) {
sections.push(` ${fromNode} --> ${toNode}`);
}
}
sections.push('```');
return sections.join('\n');
}
//# sourceMappingURL=generator.js.map