bugger-mcp
Version:
MCP Server for managing bugs, feature requests, and improvements
896 lines (766 loc) • 29.2 kB
text/typescript
// Dependency analysis system for import/require analysis and file relationship mapping
import * as fs from 'fs';
import * as path from 'path';
export interface ImportStatement {
source: string;
imported: string[];
type: 'import' | 'require' | 'dynamic';
isTypeOnly: boolean;
isDefault: boolean;
alias?: string;
line: number;
}
export interface ExportStatement {
exported: string[];
type: 'export' | 'module.exports' | 'exports';
isDefault: boolean;
source?: string;
line: number;
}
export interface DependencyRelationship {
from: string;
to: string;
type: 'import' | 'require' | 'dynamic' | 'inheritance' | 'composition' | 'usage';
strength: number;
imports: string[];
line: number;
}
export interface FileRelationship {
filePath: string;
dependencies: string[];
dependents: string[];
imports: ImportStatement[];
exports: ExportStatement[];
cyclicDependencies: string[];
relationshipStrength: number;
isEntryPoint: boolean;
isLeafNode: boolean;
}
export interface DependencyGraph {
nodes: Map<string, FileRelationship>;
edges: DependencyRelationship[];
entryPoints: string[];
leafNodes: string[];
cyclicDependencies: string[][];
clusters: string[][];
metrics: {
totalFiles: number;
totalDependencies: number;
averageDependencies: number;
maxDependencies: number;
cyclicDependencyCount: number;
cohesion: number;
coupling: number;
};
}
export interface ModuleSystem {
type: 'commonjs' | 'esmodule' | 'amd' | 'umd' | 'mixed';
confidence: number;
examples: string[];
}
export interface DependencyAnalysisOptions {
includeExtensions: string[];
excludePatterns: string[];
followSymlinks: boolean;
resolveAliases: boolean;
includeNodeModules: boolean;
maxDepth: number;
detectCircularDependencies: boolean;
analyzeTypeImports: boolean;
}
/**
* Main dependency analysis engine
*/
export class DependencyAnalyzer {
private readonly defaultExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
private readonly defaultExcludePatterns = [
'node_modules',
'.git',
'dist',
'build',
'coverage',
'__pycache__',
'.venv',
'venv',
'target',
'bin',
'obj'
];
private aliasMap: Map<string, string> = new Map();
private baseUrl: string = '';
constructor(private rootPath: string = process.cwd()) {
this.loadPathAliases();
}
/**
* Analyze imports and requires in a file
*/
public async analyzeImports(filePath: string): Promise<ImportStatement[]> {
try {
const content = fs.readFileSync(filePath, 'utf8');
return this.extractImports(content, filePath);
} catch (error) {
console.error(`Error analyzing imports in ${filePath}:`, error);
return [];
}
}
/**
* Analyze exports in a file
*/
public async analyzeExports(filePath: string): Promise<ExportStatement[]> {
try {
const content = fs.readFileSync(filePath, 'utf8');
return this.extractExports(content, filePath);
} catch (error) {
console.error(`Error analyzing exports in ${filePath}:`, error);
return [];
}
}
/**
* Build complete dependency graph for the project
*/
public async buildDependencyGraph(options: DependencyAnalysisOptions = this.getDefaultOptions()): Promise<DependencyGraph> {
const files = await this.findSourceFiles(options);
const nodes = new Map<string, FileRelationship>();
const edges: DependencyRelationship[] = [];
// First pass: collect all imports and exports
for (const file of files) {
const imports = await this.analyzeImports(file);
const exports = await this.analyzeExports(file);
const fileRelationship: FileRelationship = {
filePath: file,
dependencies: [],
dependents: [],
imports,
exports,
cyclicDependencies: [],
relationshipStrength: 0,
isEntryPoint: false,
isLeafNode: false
};
nodes.set(file, fileRelationship);
}
// Second pass: resolve dependencies and build relationships
for (const [filePath, fileRel] of nodes.entries()) {
for (const importStmt of fileRel.imports) {
const resolvedPath = await this.resolveModulePath(importStmt.source, filePath, options);
if (resolvedPath && nodes.has(resolvedPath)) {
// Add to dependencies
fileRel.dependencies.push(resolvedPath);
// Add to dependents
const dependentFile = nodes.get(resolvedPath);
if (dependentFile) {
dependentFile.dependents.push(filePath);
}
// Create edge
edges.push({
from: filePath,
to: resolvedPath,
type: importStmt.type as any,
strength: this.calculateDependencyStrength(importStmt),
imports: importStmt.imported,
line: importStmt.line
});
}
}
}
// Third pass: detect entry points and leaf nodes
for (const [filePath, fileRel] of nodes.entries()) {
fileRel.isEntryPoint = fileRel.dependents.length === 0 && fileRel.dependencies.length > 0;
fileRel.isLeafNode = fileRel.dependencies.length === 0 && fileRel.dependents.length > 0;
fileRel.relationshipStrength = this.calculateRelationshipStrength(fileRel);
}
// Detect circular dependencies
let cyclicDependencies: string[][] = [];
if (options.detectCircularDependencies) {
cyclicDependencies = this.detectCircularDependencies(nodes);
// Add cyclic dependencies to nodes
for (const cycle of cyclicDependencies) {
for (const file of cycle) {
const fileRel = nodes.get(file);
if (fileRel) {
fileRel.cyclicDependencies = cycle.filter(f => f !== file);
}
}
}
}
// Detect clusters
const clusters = this.detectClusters(nodes, edges);
// Calculate metrics
const metrics = this.calculateMetrics(nodes, edges, cyclicDependencies);
return {
nodes,
edges,
entryPoints: Array.from(nodes.values()).filter(f => f.isEntryPoint).map(f => f.filePath),
leafNodes: Array.from(nodes.values()).filter(f => f.isLeafNode).map(f => f.filePath),
cyclicDependencies,
clusters,
metrics
};
}
/**
* Map file relationships for navigation
*/
public async mapFileRelationships(
filePath: string,
options: DependencyAnalysisOptions = this.getDefaultOptions()
): Promise<FileRelationship> {
const imports = await this.analyzeImports(filePath);
const exports = await this.analyzeExports(filePath);
const dependencies: string[] = [];
const dependents: string[] = [];
// Resolve dependencies
for (const importStmt of imports) {
const resolvedPath = await this.resolveModulePath(importStmt.source, filePath, options);
if (resolvedPath) {
dependencies.push(resolvedPath);
}
}
// Find dependents (files that import this file)
const allFiles = await this.findSourceFiles(options);
for (const file of allFiles) {
if (file === filePath) continue;
const fileImports = await this.analyzeImports(file);
for (const importStmt of fileImports) {
const resolvedPath = await this.resolveModulePath(importStmt.source, file, options);
if (resolvedPath === filePath) {
dependents.push(file);
break;
}
}
}
return {
filePath,
dependencies,
dependents,
imports,
exports,
cyclicDependencies: [],
relationshipStrength: this.calculateRelationshipStrength({
dependencies,
dependents
} as any),
isEntryPoint: dependents.length === 0 && dependencies.length > 0,
isLeafNode: dependencies.length === 0 && dependents.length > 0
};
}
/**
* Detect module system used in the project
*/
public async detectModuleSystem(options: DependencyAnalysisOptions = this.getDefaultOptions()): Promise<ModuleSystem> {
const files = await this.findSourceFiles(options);
const systemCounts = {
commonjs: 0,
esmodule: 0,
amd: 0,
umd: 0
};
const examples: string[] = [];
for (const file of files.slice(0, 50)) { // Sample first 50 files
try {
const content = fs.readFileSync(file, 'utf8');
// Check for CommonJS
if (content.includes('require(') || content.includes('module.exports') || content.includes('exports.')) {
systemCounts.commonjs++;
if (examples.length < 3) examples.push(`${file}: require/module.exports`);
}
// Check for ES Modules
if (content.includes('import ') || content.includes('export ')) {
systemCounts.esmodule++;
if (examples.length < 3) examples.push(`${file}: import/export`);
}
// Check for AMD
if (content.includes('define(') || content.includes('require.config')) {
systemCounts.amd++;
if (examples.length < 3) examples.push(`${file}: AMD define`);
}
// Check for UMD
if (content.includes('typeof exports') && content.includes('typeof module') && content.includes('define.amd')) {
systemCounts.umd++;
if (examples.length < 3) examples.push(`${file}: UMD pattern`);
}
} catch (error) {
console.error(`Error analyzing module system in ${file}:`, error);
}
}
const total = Object.values(systemCounts).reduce((sum, count) => sum + count, 0);
const maxCount = Math.max(...Object.values(systemCounts));
let dominantSystem: ModuleSystem['type'] = 'mixed';
let confidence = 0;
if (total > 0) {
if (systemCounts.esmodule === maxCount) {
dominantSystem = 'esmodule';
confidence = systemCounts.esmodule / total;
} else if (systemCounts.commonjs === maxCount) {
dominantSystem = 'commonjs';
confidence = systemCounts.commonjs / total;
} else if (systemCounts.amd === maxCount) {
dominantSystem = 'amd';
confidence = systemCounts.amd / total;
} else if (systemCounts.umd === maxCount) {
dominantSystem = 'umd';
confidence = systemCounts.umd / total;
}
// If multiple systems have significant usage, it's mixed
const significantSystems = Object.values(systemCounts).filter(count => count / total > 0.2);
if (significantSystems.length > 1) {
dominantSystem = 'mixed';
confidence = 1 - (maxCount / total);
}
}
return {
type: dominantSystem,
confidence,
examples
};
}
// Private helper methods
private async findSourceFiles(options: DependencyAnalysisOptions): Promise<string[]> {
const files: string[] = [];
const traverse = async (dir: string, currentDepth: number = 0) => {
if (currentDepth > options.maxDepth) return;
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (this.shouldExclude(fullPath, options.excludePatterns)) {
continue;
}
if (entry.isDirectory()) {
await traverse(fullPath, currentDepth + 1);
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (options.includeExtensions.includes(ext)) {
files.push(fullPath);
}
} else if (entry.isSymbolicLink() && options.followSymlinks) {
const resolvedPath = fs.readlinkSync(fullPath);
if (fs.existsSync(resolvedPath)) {
const stat = fs.statSync(resolvedPath);
if (stat.isDirectory()) {
await traverse(resolvedPath, currentDepth + 1);
} else if (stat.isFile()) {
const ext = path.extname(resolvedPath).toLowerCase();
if (options.includeExtensions.includes(ext)) {
files.push(resolvedPath);
}
}
}
}
}
} catch (error) {
console.error(`Error traversing directory ${dir}:`, error);
}
};
await traverse(this.rootPath);
return files;
}
private extractImports(content: string, filePath: string): ImportStatement[] {
const imports: ImportStatement[] = [];
const lines = content.split('\n');
// ES Module imports
const esImportPatterns = [
// import { named } from 'module'
/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g,
// import defaultExport from 'module'
/import\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s*['"]([^'"]+)['"]/g,
// import defaultExport, { named } from 'module'
/import\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g,
// import * as namespace from 'module'
/import\s*\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s*['"]([^'"]+)['"]/g,
// import 'module' (side effect)
/import\s*['"]([^'"]+)['"]/g,
// import type { Type } from 'module'
/import\s+type\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/g,
// import type DefaultType from 'module'
/import\s+type\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from\s*['"]([^'"]+)['"]/g
];
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];
for (const pattern of esImportPatterns) {
let match;
while ((match = pattern.exec(line)) !== null) {
const importStmt = this.parseESImport(match, lineIndex + 1);
if (importStmt) {
imports.push(importStmt);
}
}
}
// CommonJS require()
const requirePattern = /(?:const|let|var)\s+(?:\{([^}]+)\}|([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
let requireMatch;
while ((requireMatch = requirePattern.exec(line)) !== null) {
const imported = requireMatch[1] ?
requireMatch[1].split(',').map(s => s.trim()) :
requireMatch[2] ? [requireMatch[2]] : [];
imports.push({
source: requireMatch[3],
imported,
type: 'require',
isTypeOnly: false,
isDefault: !requireMatch[1], // If not destructured, it's default
line: lineIndex + 1
});
}
// Dynamic imports
const dynamicImportPattern = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
let dynamicMatch;
while ((dynamicMatch = dynamicImportPattern.exec(line)) !== null) {
imports.push({
source: dynamicMatch[1],
imported: [],
type: 'dynamic',
isTypeOnly: false,
isDefault: false,
line: lineIndex + 1
});
}
}
return imports;
}
private extractExports(content: string, filePath: string): ExportStatement[] {
const exports: ExportStatement[] = [];
const lines = content.split('\n');
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];
// ES Module exports
const esExportPatterns = [
// export { named }
{ pattern: /export\s*\{\s*([^}]+)\s*\}/, isDefault: false },
// export default
{ pattern: /export\s+default\s+/, isDefault: true },
// export const/let/var
{ pattern: /export\s+(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$,\s]*)/g, isDefault: false },
// export function
{ pattern: /export\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, isDefault: false },
// export class
{ pattern: /export\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g, isDefault: false },
// export { named } from 'module'
{ pattern: /export\s*\{\s*([^}]+)\s*\}\s*from\s*['"]([^'"]+)['"]/, isDefault: false },
// export * from 'module'
{ pattern: /export\s*\*\s*from\s*['"]([^'"]+)['"]/, isDefault: false }
];
for (const { pattern, isDefault } of esExportPatterns) {
const match = pattern.exec(line);
if (match) {
const exported = isDefault ? ['default'] :
match[1] ? match[1].split(',').map(s => s.trim()) : ['*'];
exports.push({
exported,
type: 'export',
isDefault,
source: match[2],
line: lineIndex + 1
});
}
}
// CommonJS module.exports
const moduleExportsPattern = /module\.exports\s*=\s*(.+)/;
const moduleExportsMatch = moduleExportsPattern.exec(line);
if (moduleExportsMatch) {
exports.push({
exported: ['default'],
type: 'module.exports',
isDefault: true,
line: lineIndex + 1
});
}
// CommonJS exports.property
const exportsPattern = /exports\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/;
const exportsMatch = exportsPattern.exec(line);
if (exportsMatch) {
exports.push({
exported: [exportsMatch[1]],
type: 'exports',
isDefault: false,
line: lineIndex + 1
});
}
}
return exports;
}
private parseESImport(match: RegExpExecArray, lineNumber: number): ImportStatement | null {
const fullMatch = match[0];
// Import type detection
const isTypeOnly = fullMatch.includes('import type');
// Extract source (always the last capture group for module path)
const source = match[match.length - 1];
// Determine import type and parse imported items
let imported: string[] = [];
let isDefault = false;
let alias: string | undefined;
if (fullMatch.includes('* as ')) {
// import * as namespace from 'module'
const namespaceMatch = fullMatch.match(/\*\s*as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/);
if (namespaceMatch) {
imported = ['*'];
alias = namespaceMatch[1];
}
} else if (fullMatch.includes('{')) {
// import { named } from 'module' or import default, { named } from 'module'
const namedMatch = fullMatch.match(/\{\s*([^}]+)\s*\}/);
if (namedMatch) {
imported = namedMatch[1].split(',').map(s => s.trim());
}
// Check for default import combined with named imports
const defaultMatch = fullMatch.match(/import\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*,/);
if (defaultMatch) {
imported.unshift(defaultMatch[1]);
isDefault = true;
}
} else if (fullMatch.match(/import\s+[a-zA-Z_$][a-zA-Z0-9_$]*\s+from/)) {
// import defaultExport from 'module'
const defaultMatch = fullMatch.match(/import\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s+from/);
if (defaultMatch) {
imported = [defaultMatch[1]];
isDefault = true;
}
} else if (fullMatch.match(/import\s*['"]([^'"]+)['"]/)) {
// import 'module' (side effect)
imported = [];
isDefault = false;
}
return {
source,
imported,
type: 'import',
isTypeOnly,
isDefault,
...(alias && { alias }),
line: lineNumber
};
}
private async resolveModulePath(
source: string,
currentFile: string,
options: DependencyAnalysisOptions
): Promise<string | null> {
// Handle relative paths
if (source.startsWith('./') || source.startsWith('../')) {
const currentDir = path.dirname(currentFile);
const resolvedPath = path.resolve(currentDir, source);
// Try different extensions
for (const ext of options.includeExtensions) {
const pathWithExt = resolvedPath + ext;
if (fs.existsSync(pathWithExt)) {
return pathWithExt;
}
}
// Try index files
for (const ext of options.includeExtensions) {
const indexPath = path.join(resolvedPath, `index${ext}`);
if (fs.existsSync(indexPath)) {
return indexPath;
}
}
return null;
}
// Handle absolute paths (rare in imports)
if (path.isAbsolute(source)) {
return fs.existsSync(source) ? source : null;
}
// Handle path aliases
if (options.resolveAliases && this.aliasMap.has(source)) {
const aliasPath = this.aliasMap.get(source)!;
return await this.resolveModulePath(aliasPath, currentFile, options);
}
// Handle node_modules (if included)
if (options.includeNodeModules) {
const nodeModulesPath = path.join(this.rootPath, 'node_modules', source);
if (fs.existsSync(nodeModulesPath)) {
return nodeModulesPath;
}
}
return null;
}
private calculateDependencyStrength(importStmt: ImportStatement): number {
let strength = 0.5; // Base strength
// Type-only imports have lower strength
if (importStmt.isTypeOnly) {
strength *= 0.5;
}
// Dynamic imports have lower strength
if (importStmt.type === 'dynamic') {
strength *= 0.7;
}
// More imported items = higher strength
if (importStmt.imported.length > 0) {
strength += Math.min(0.3, importStmt.imported.length * 0.1);
}
// Default imports have higher strength
if (importStmt.isDefault) {
strength += 0.1;
}
return Math.min(1.0, strength);
}
private calculateRelationshipStrength(fileRel: Partial<FileRelationship>): number {
const depCount = fileRel.dependencies?.length || 0;
const dependendCount = fileRel.dependents?.length || 0;
// Normalize based on total connections
const totalConnections = depCount + dependendCount;
if (totalConnections === 0) return 0;
// Higher strength for files with more connections
return Math.min(1.0, totalConnections / 10);
}
private detectCircularDependencies(nodes: Map<string, FileRelationship>): string[][] {
const cycles: string[][] = [];
const visited = new Set<string>();
const recursionStack = new Set<string>();
const dfs = (node: string, path: string[]): void => {
if (recursionStack.has(node)) {
// Found a cycle
const cycleStart = path.indexOf(node);
const cycle = path.slice(cycleStart);
cycles.push([...cycle, node]);
return;
}
if (visited.has(node)) {
return;
}
visited.add(node);
recursionStack.add(node);
const fileRel = nodes.get(node);
if (fileRel) {
for (const dep of fileRel.dependencies) {
dfs(dep, [...path, node]);
}
}
recursionStack.delete(node);
};
for (const node of nodes.keys()) {
if (!visited.has(node)) {
dfs(node, []);
}
}
return cycles;
}
private detectClusters(nodes: Map<string, FileRelationship>, edges: DependencyRelationship[]): string[][] {
const clusters: string[][] = [];
const visited = new Set<string>();
// Simple clustering based on strong connections
for (const [filePath, fileRel] of nodes.entries()) {
if (visited.has(filePath)) continue;
const cluster = new Set<string>([filePath]);
const queue = [filePath];
while (queue.length > 0) {
const current = queue.shift()!;
visited.add(current);
const currentRel = nodes.get(current);
if (!currentRel) continue;
// Add strongly connected dependencies and dependents
for (const dep of currentRel.dependencies) {
if (!visited.has(dep)) {
const edge = edges.find(e => e.from === current && e.to === dep);
if (edge && edge.strength > 0.7) {
cluster.add(dep);
queue.push(dep);
}
}
}
for (const dependent of currentRel.dependents) {
if (!visited.has(dependent)) {
const edge = edges.find(e => e.from === dependent && e.to === current);
if (edge && edge.strength > 0.7) {
cluster.add(dependent);
queue.push(dependent);
}
}
}
}
if (cluster.size > 1) {
clusters.push(Array.from(cluster));
}
}
return clusters;
}
private calculateMetrics(
nodes: Map<string, FileRelationship>,
edges: DependencyRelationship[],
cyclicDependencies: string[][]
): DependencyGraph['metrics'] {
const totalFiles = nodes.size;
const totalDependencies = edges.length;
const averageDependencies = totalFiles > 0 ? totalDependencies / totalFiles : 0;
let maxDependencies = 0;
for (const fileRel of nodes.values()) {
maxDependencies = Math.max(maxDependencies, fileRel.dependencies.length);
}
const cyclicDependencyCount = cyclicDependencies.length;
// Calculate cohesion (how well connected modules within clusters are)
const cohesion = this.calculateCohesion(nodes, edges);
// Calculate coupling (how much modules depend on each other)
const coupling = this.calculateCoupling(nodes, edges);
return {
totalFiles,
totalDependencies,
averageDependencies,
maxDependencies,
cyclicDependencyCount,
cohesion,
coupling
};
}
private calculateCohesion(nodes: Map<string, FileRelationship>, edges: DependencyRelationship[]): number {
if (nodes.size === 0) return 0;
let totalCohesion = 0;
let clusterCount = 0;
// Simple cohesion calculation based on internal connections
for (const fileRel of nodes.values()) {
const internalConnections = fileRel.dependencies.filter(dep =>
nodes.has(dep) && nodes.get(dep)!.dependencies.includes(fileRel.filePath)
).length;
const totalConnections = fileRel.dependencies.length + fileRel.dependents.length;
if (totalConnections > 0) {
totalCohesion += internalConnections / totalConnections;
clusterCount++;
}
}
return clusterCount > 0 ? totalCohesion / clusterCount : 0;
}
private calculateCoupling(nodes: Map<string, FileRelationship>, edges: DependencyRelationship[]): number {
if (nodes.size === 0) return 0;
// Calculate average dependencies per file
const avgDependencies = edges.length / nodes.size;
// Normalize to 0-1 range (assuming 10 dependencies is high coupling)
return Math.min(1.0, avgDependencies / 10);
}
private loadPathAliases(): void {
// Load path aliases from tsconfig.json or similar
try {
const tsconfigPath = path.join(this.rootPath, 'tsconfig.json');
if (fs.existsSync(tsconfigPath)) {
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
const compilerOptions = tsconfig.compilerOptions;
if (compilerOptions) {
this.baseUrl = compilerOptions.baseUrl || '';
if (compilerOptions.paths) {
for (const [alias, paths] of Object.entries(compilerOptions.paths)) {
if (Array.isArray(paths) && paths.length > 0) {
const resolvedPath = path.resolve(this.rootPath, this.baseUrl, paths[0].replace('/*', ''));
this.aliasMap.set(alias.replace('/*', ''), resolvedPath);
}
}
}
}
}
} catch (error) {
console.error('Error loading path aliases:', error);
}
}
private shouldExclude(filePath: string, excludePatterns: string[]): boolean {
return excludePatterns.some(pattern =>
filePath.includes(pattern) || filePath.includes(path.sep + pattern + path.sep)
);
}
private getDefaultOptions(): DependencyAnalysisOptions {
return {
includeExtensions: this.defaultExtensions,
excludePatterns: this.defaultExcludePatterns,
followSymlinks: false,
resolveAliases: true,
includeNodeModules: false,
maxDepth: 10,
detectCircularDependencies: true,
analyzeTypeImports: true
};
}
}