@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
364 lines • 13.5 kB
JavaScript
"use strict";
/* eslint-disable max-lines */
/* eslint-disable max-lines-per-function */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractFileDependencies = extractFileDependencies;
exports.analyzeDependencies = analyzeDependencies;
exports.resolveModulePath = resolveModulePath;
const parser_1 = require("@babel/parser");
const traverse_1 = __importDefault(require("@babel/traverse"));
const t = __importStar(require("@babel/types"));
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
// Babel parser configuration for dependency analysis
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const DEPENDENCY_PARSE_CONFIG = {
sourceType: 'module',
plugins: [
'typescript',
'jsx',
'decorators-legacy',
'classProperties',
'asyncGenerators',
'bigInt',
'dynamicImport',
],
};
/**
* Extract dependencies from a single file's AST
*/
function extractFileDependencies(filePath, content) {
const dependencies = [];
try {
const ast = (0, parser_1.parse)(content, DEPENDENCY_PARSE_CONFIG);
(0, traverse_1.default)(ast, {
ImportDeclaration(nodePath) {
const source = nodePath.node.source.value;
const isExternal = !source.startsWith('./') &&
!source.startsWith('../') &&
!source.startsWith('/');
// Handle different import types
nodePath.node.specifiers.forEach(spec => {
if (t.isImportDefaultSpecifier(spec)) {
dependencies.push({
from: filePath,
to: source,
type: 'import',
importName: spec.local.name,
isExternal,
importType: 'default',
});
}
else if (t.isImportSpecifier(spec)) {
const importName = t.isIdentifier(spec.imported)
? spec.imported.name
: spec.imported.value;
dependencies.push({
from: filePath,
to: source,
type: 'import',
importName,
isExternal,
importType: 'named',
});
}
else if (t.isImportNamespaceSpecifier(spec)) {
dependencies.push({
from: filePath,
to: source,
type: 'import',
importName: spec.local.name,
isExternal,
importType: 'namespace',
});
}
});
// Handle side-effect imports (no specifiers)
if (nodePath.node.specifiers.length === 0) {
dependencies.push({
from: filePath,
to: source,
type: 'import',
isExternal,
importType: 'side-effect',
});
}
},
ExportNamedDeclaration(nodePath) {
if (nodePath.node.source) {
const source = nodePath.node.source.value;
const isExternal = !source.startsWith('./') &&
!source.startsWith('../') &&
!source.startsWith('/');
nodePath.node.specifiers?.forEach(spec => {
if (t.isExportSpecifier(spec)) {
const exportName = t.isIdentifier(spec.exported)
? spec.exported.name
: spec.exported.value;
dependencies.push({
from: filePath,
to: source,
type: 'export',
importName: exportName,
isExternal,
importType: 'named',
});
}
});
}
},
ExportAllDeclaration(nodePath) {
if (nodePath.node.source) {
const source = nodePath.node.source.value;
const isExternal = !source.startsWith('./') &&
!source.startsWith('../') &&
!source.startsWith('/');
dependencies.push({
from: filePath,
to: source,
type: 'export',
isExternal,
importType: 'namespace',
});
}
},
// Handle type-only imports
TSImportType(nodePath) {
if (t.isStringLiteral(nodePath.node.argument)) {
const source = nodePath.node.argument.value;
const isExternal = !source.startsWith('./') &&
!source.startsWith('../') &&
!source.startsWith('/');
dependencies.push({
from: filePath,
to: source,
type: 'type',
isExternal,
importType: 'named',
});
}
},
});
}
catch (error) {
// Fail-fast: throw parse errors immediately
throw new Error(`Failed to parse dependencies in ${filePath}: ${error.message}`);
}
return dependencies;
}
/**
* Analyze dependencies for an array of files
*/
function analyzeDependencies(files, options = {}) {
if (files.length === 0) {
throw new Error('No files provided for dependency analysis');
}
const allDependencies = [];
const projectPath = findCommonPath(files);
// Extract dependencies from each file
for (const file of files) {
try {
const content = (0, fs_1.readFileSync)(file, 'utf-8');
const fileDeps = extractFileDependencies(file, content);
allDependencies.push(...fileDeps);
}
catch (error) {
throw new Error(`Failed to analyze file ${file}: ${error.message}`);
}
}
// Build unique node list
const nodeSet = new Set();
files.forEach(file => nodeSet.add(file));
// Add external dependencies to nodes if requested
if (options.includeExternalDeps) {
allDependencies
.filter(dep => dep.isExternal)
.forEach(dep => nodeSet.add(dep.to));
}
// Generate metrics
const metrics = calculateGraphMetrics(Array.from(nodeSet), allDependencies, options);
return {
projectPath,
nodes: Array.from(nodeSet).sort(),
edges: allDependencies,
metrics,
};
}
/**
* Calculate graph metrics
*/
function calculateGraphMetrics(nodes, edges, options) {
const internalEdges = edges.filter(edge => !edge.isExternal);
const externalEdges = edges.filter(edge => edge.isExternal);
// Find most connected module
const connectionCounts = new Map();
internalEdges.forEach(edge => {
connectionCounts.set(edge.from, (connectionCounts.get(edge.from) || 0) + 1);
});
const mostConnectedModule = Array.from(connectionCounts.entries()).sort(([, a], [, b]) => b - a)[0]?.[0];
// Calculate average dependencies per module
const averageDependencies = nodes.length > 0
? Math.round((internalEdges.length / nodes.length) * 10) / 10
: 0;
// Detect circular dependencies if requested
const circularDependencies = options.detectCircular
? detectCircularDependencies(nodes, internalEdges)
: [];
return {
totalNodes: nodes.length,
totalEdges: edges.length,
internalDependencies: internalEdges.length,
externalDependencies: externalEdges.length,
circularDependencies,
mostConnectedModule,
averageDependencies,
};
}
/**
* Detect circular dependencies using DFS
*/
function detectCircularDependencies(nodes, edges) {
const adjList = new Map();
const cycles = [];
// Build adjacency list
nodes.forEach(node => adjList.set(node, []));
edges.forEach(edge => {
if (!edge.isExternal) {
const targets = adjList.get(edge.from) || [];
targets.push(edge.to);
adjList.set(edge.from, targets);
}
});
// DFS to detect cycles
const visited = new Set();
const recursionStack = new Set();
const path = [];
function dfs(node) {
if (recursionStack.has(node)) {
// Found cycle - extract it from path
const cycleStart = path.indexOf(node);
if (cycleStart >= 0) {
cycles.push([...path.slice(cycleStart), node]);
}
return true;
}
if (visited.has(node)) {
return false;
}
visited.add(node);
recursionStack.add(node);
path.push(node);
const neighbors = adjList.get(node) || [];
for (const neighbor of neighbors) {
if (dfs(neighbor)) {
// Continue to find all cycles
}
}
recursionStack.delete(node);
path.pop();
return false;
}
// Check each node
nodes.forEach(node => {
if (!visited.has(node)) {
dfs(node);
}
});
return cycles;
}
/**
* Find common path prefix for a list of files
*/
function findCommonPath(files) {
if (files.length === 0)
return '';
if (files.length === 1)
return path_1.default.dirname(files[0]);
const absolutePaths = files.map(file => path_1.default.resolve(file));
const pathParts = absolutePaths.map(p => p.split(path_1.default.sep));
const commonParts = [];
const minLength = Math.min(...pathParts.map(parts => parts.length));
for (let i = 0; i < minLength; i++) {
const part = pathParts[0][i];
if (pathParts.every(parts => parts[i] === part)) {
commonParts.push(part);
}
else {
break;
}
}
return commonParts.join(path_1.default.sep) || '/';
}
/**
* Resolve relative paths to absolute paths within project
*/
function resolveModulePath(fromFile, toModule) {
// Handle relative imports
if (toModule.startsWith('./') || toModule.startsWith('../')) {
const fromDir = path_1.default.dirname(fromFile);
const resolved = path_1.default.resolve(fromDir, toModule);
// Try common TypeScript/JavaScript extensions
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.d.ts'];
for (const ext of extensions) {
const withExt = resolved + ext;
try {
(0, fs_1.readFileSync)(withExt);
return withExt;
}
catch {
// Continue trying other extensions
}
}
// Try index files
for (const ext of extensions) {
const indexFile = path_1.default.join(resolved, `index${ext}`);
try {
(0, fs_1.readFileSync)(indexFile);
return indexFile;
}
catch {
// Continue trying other index files
}
}
return resolved; // Return as-is if file not found
}
// Return external modules as-is
return toModule;
}
//# sourceMappingURL=dependency-analyzer.js.map