code-auditor-mcp
Version:
Multi-language code quality auditor with MCP server - Analyze TypeScript, JavaScript, and Go code for SOLID principles, DRY violations, security patterns, and more
237 lines • 9 kB
JavaScript
/**
* Dependency extraction utilities for function-level dependency tracking
*/
import * as ts from 'typescript';
/**
* Extract all function calls within a given AST node
*/
export function extractFunctionCalls(node, sourceFile, importMap) {
const calls = [];
function visit(node) {
if (ts.isCallExpression(node)) {
const callInfo = resolveCallExpression(node, sourceFile, importMap);
if (callInfo) {
calls.push(callInfo);
}
}
ts.forEachChild(node, visit);
}
visit(node);
return calls;
}
/**
* Build a map of imports from import statements
*/
export function buildImportMap(sourceFile) {
const importMap = new Map();
function visit(node) {
if (ts.isImportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier.text;
const importClause = node.importClause;
if (importClause) {
// Default import
if (importClause.name) {
const localName = importClause.name.text;
importMap.set(localName, {
localName,
importedName: 'default',
modulePath: moduleSpecifier,
importType: 'default',
isTypeOnly: importClause.isTypeOnly || false
});
}
// Named imports
if (importClause.namedBindings) {
if (ts.isNamedImports(importClause.namedBindings)) {
importClause.namedBindings.elements.forEach(element => {
const localName = element.name.text;
const importedName = element.propertyName?.text || localName;
importMap.set(localName, {
localName,
importedName,
modulePath: moduleSpecifier,
importType: 'named',
isTypeOnly: importClause.isTypeOnly || element.isTypeOnly || false
});
});
}
else if (ts.isNamespaceImport(importClause.namedBindings)) {
// Namespace import (import * as name from 'module')
const localName = importClause.namedBindings.name.text;
importMap.set(localName, {
localName,
importedName: '*',
modulePath: moduleSpecifier,
importType: 'namespace',
isTypeOnly: importClause.isTypeOnly || false
});
}
}
}
}
else if (ts.isVariableStatement(node)) {
// Handle require statements (CommonJS)
node.declarationList.declarations.forEach(decl => {
if (ts.isVariableDeclaration(decl) && decl.initializer && ts.isCallExpression(decl.initializer)) {
const callExpr = decl.initializer;
if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === 'require' &&
callExpr.arguments.length > 0 && ts.isStringLiteral(callExpr.arguments[0])) {
const modulePath = callExpr.arguments[0].text;
if (ts.isIdentifier(decl.name)) {
const localName = decl.name.text;
importMap.set(localName, {
localName,
importedName: 'default',
modulePath,
importType: 'default',
isTypeOnly: false
});
}
}
}
});
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return importMap;
}
/**
* Resolve a call expression to get call information
*/
export function resolveCallExpression(callExpr, sourceFile, importMap) {
const expr = callExpr.expression;
const { line, character } = sourceFile.getLineAndCharacterOfPosition(callExpr.getStart());
let callee;
let callType = 'direct';
if (ts.isIdentifier(expr)) {
// Direct function call: functionName()
callee = expr.text;
callType = 'direct';
}
else if (ts.isPropertyAccessExpression(expr)) {
// Method call: object.method()
callee = resolvePropertyAccess(expr, importMap);
callType = 'method';
}
else if (ts.isElementAccessExpression(expr)) {
// Dynamic call: object[property]()
callee = '[dynamic]';
callType = 'dynamic';
}
if (callee) {
return {
callee,
callType,
line: line + 1, // Convert to 1-based
column: character + 1,
arguments: callExpr.arguments.length
};
}
return undefined;
}
/**
* Resolve property access expressions to a string representation
*/
function resolvePropertyAccess(expr, importMap) {
const parts = [expr.name.text];
let current = expr.expression;
while (ts.isPropertyAccessExpression(current)) {
parts.unshift(current.name.text);
current = current.expression;
}
if (ts.isIdentifier(current)) {
const baseName = current.text;
const importInfo = importMap.get(baseName);
if (importInfo) {
// Imported module method call
parts.unshift(`${importInfo.modulePath}#${baseName}`);
}
else {
// Local object method call
parts.unshift(baseName);
}
}
return parts.join('.');
}
/**
* Extract identifier usage within a function to determine which imports are actually used
*/
export function extractIdentifierUsage(node, sourceFile, importMap) {
const usageMap = new Map();
function visit(node) {
if (ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node.parent) &&
!ts.isPropertyAssignment(node.parent)) {
const name = node.text;
if (importMap.has(name)) {
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const existing = usageMap.get(name) || {
usageType: 'direct',
usageCount: 0,
lineNumbers: []
};
existing.usageCount++;
existing.lineNumbers.push(line + 1);
// Determine usage type
if (ts.isTypeNode(node.parent) || ts.isTypeReferenceNode(node.parent)) {
existing.usageType = 'type';
}
else if (ts.isExportSpecifier(node.parent)) {
existing.usageType = 'reexport';
}
usageMap.set(name, existing);
}
}
ts.forEachChild(node, visit);
}
visit(node);
return usageMap;
}
/**
* Get all local function names defined in the file
*/
export function getLocalFunctionNames(sourceFile) {
const functionNames = new Set();
function visit(node) {
if (ts.isFunctionDeclaration(node) && node.name) {
functionNames.add(node.name.text);
}
else if (ts.isVariableStatement(node)) {
node.declarationList.declarations.forEach(decl => {
if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name) &&
decl.initializer && (ts.isFunctionExpression(decl.initializer) ||
ts.isArrowFunction(decl.initializer))) {
functionNames.add(decl.name.text);
}
});
}
else if (ts.isClassDeclaration(node) && node.name) {
functionNames.add(node.name.text);
// Also add class methods
node.members.forEach(member => {
if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
functionNames.add(`${node.name.text}.${member.name.text}`);
}
});
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return functionNames;
}
/**
* Normalize a function call target for consistent naming
*/
export function normalizeCallTarget(callee, filePath, localFunctions) {
// If it's a local function, prefix with file path for uniqueness
if (localFunctions.has(callee)) {
return `${filePath}#${callee}`;
}
// If it already has a module path, return as-is
if (callee.includes('#')) {
return callee;
}
// Otherwise, it's an unresolved external call
return callee;
}
//# sourceMappingURL=dependencyExtractor.js.map