Skip to main content
Glama
FosterG4

Code Reference Optimizer MCP Server

by FosterG4
ScopeAnalyzer.ts23.7 kB
import * as path from 'path'; import type { SymbolInfo } from '../types/index.js'; import type { SemanticContext } from './SemanticAnalyzer.js'; import { SemanticAnalyzer } from './SemanticAnalyzer.js'; export interface ScopeNode { id: string; name: string; type: 'global' | 'module' | 'class' | 'function' | 'block' | 'namespace' | 'closure'; startLine: number; endLine: number; parent: ScopeNode | null; children: ScopeNode[]; symbols: Map<string, SymbolInfo>; variables: Map<string, VariableInfo>; accessibleSymbols: Set<string>; // All symbols accessible in this scope file: string; } export interface VariableInfo { name: string; type: string; declarationLine: number; scope: string; isParameter: boolean; isConstant: boolean; isHoisted: boolean; shadowedBy: string[]; // Variables that shadow this one shadows: string | null; // Variable this one shadows usages: Array<{ line: number; type: 'read' | 'write' | 'readwrite'; context: string; }>; } export interface ScopeHierarchy { root: ScopeNode; allScopes: Map<string, ScopeNode>; scopesByType: Map<ScopeNode['type'], ScopeNode[]>; variableResolution: Map<string, VariableInfo[]>; } export interface ScopeAnalysisResult { hierarchy: ScopeHierarchy; shadowingIssues: ShadowingIssue[]; unusedVariables: UnusedVariable[]; scopeViolations: ScopeViolation[]; closureAnalysis: ClosureAnalysis[]; variableLifetime: Map<string, VariableLifetime>; } export interface ShadowingIssue { shadowingVariable: string; shadowedVariable: string; shadowingScope: string; shadowedScope: string; line: number; severity: 'warning' | 'error'; suggestion: string; } export interface UnusedVariable { name: string; scope: string; declarationLine: number; type: string; reason: 'never_used' | 'write_only' | 'dead_after_assignment'; } export interface ScopeViolation { type: 'undefined_variable' | 'out_of_scope_access' | 'temporal_dead_zone' | 'const_reassignment'; variable: string; line: number; scope: string; description: string; severity: 'error' | 'warning'; } export interface ClosureAnalysis { functionName: string; scope: string; capturedVariables: Array<{ name: string; fromScope: string; captureType: 'value' | 'reference'; }>; potentialMemoryLeaks: string[]; recommendations: string[]; } export interface VariableLifetime { name: string; scope: string; declarationLine: number; firstUsage: number; lastUsage: number; totalUsages: number; lifespan: number; // Lines between declaration and last usage isLongLived: boolean; } export interface ScopeMetrics { totalScopes: number; averageScopeDepth: number; maxScopeDepth: number; scopeComplexity: Map<string, number>; variableDensity: Map<string, number>; // Variables per scope shadowingRate: number; unusedVariableRate: number; } export class ScopeAnalyzer { private semanticAnalyzer: SemanticAnalyzer; private scopeHierarchies: Map<string, ScopeHierarchy> = new Map(); private globalScopeId = 0; constructor() { this.semanticAnalyzer = new SemanticAnalyzer(); } /** * Analyze scope hierarchy for a single file */ async analyzeFileScopes(filePath: string): Promise<ScopeAnalysisResult> { const absolutePath = path.resolve(filePath); const context = await this.semanticAnalyzer.analyzeFile(absolutePath); const hierarchy = await this.buildScopeHierarchy(absolutePath, context); this.scopeHierarchies.set(absolutePath, hierarchy); const shadowingIssues = this.detectShadowingIssues(hierarchy); const unusedVariables = this.findUnusedVariables(hierarchy); const scopeViolations = this.detectScopeViolations(hierarchy); const closureAnalysis = this.analyzeClosures(hierarchy); const variableLifetime = this.analyzeVariableLifetime(hierarchy); return { hierarchy, shadowingIssues, unusedVariables, scopeViolations, closureAnalysis, variableLifetime }; } /** * Analyze scopes across multiple files */ async analyzeProjectScopes(filePaths: string[]): Promise<Map<string, ScopeAnalysisResult>> { const results = new Map<string, ScopeAnalysisResult>(); for (const filePath of filePaths) { try { const result = await this.analyzeFileScopes(filePath); results.set(path.resolve(filePath), result); } catch (error) { console.warn(`Failed to analyze scopes for ${filePath}:`, error); } } return results; } /** * Resolve a symbol in a specific scope */ resolveSymbolInScope(symbol: string, scopeId: string, filePath: string): SymbolInfo | null { const hierarchy = this.scopeHierarchies.get(path.resolve(filePath)); if (!hierarchy) return null; const scope = hierarchy.allScopes.get(scopeId); if (!scope) return null; // Search in current scope and parent scopes let currentScope: ScopeNode | null = scope; while (currentScope) { if (currentScope.symbols.has(symbol)) { return currentScope.symbols.get(symbol)!; } currentScope = currentScope.parent; } return null; } /** * Find all symbols accessible in a given scope */ getAccessibleSymbols(scopeId: string, filePath: string): Set<string> { const hierarchy = this.scopeHierarchies.get(path.resolve(filePath)); if (!hierarchy) return new Set(); const scope = hierarchy.allScopes.get(scopeId); if (!scope) return new Set(); return scope.accessibleSymbols; } /** * Find the scope containing a specific line */ findScopeAtLine(line: number, filePath: string): ScopeNode | null { const hierarchy = this.scopeHierarchies.get(path.resolve(filePath)); if (!hierarchy) return null; const findInScope = (scope: ScopeNode): ScopeNode | null => { if (line >= scope.startLine && line <= scope.endLine) { // Check children first (more specific scopes) for (const child of scope.children) { const found = findInScope(child); if (found) return found; } return scope; } return null; }; return findInScope(hierarchy.root); } /** * Calculate scope metrics */ calculateScopeMetrics(filePath: string): ScopeMetrics { const hierarchy = this.scopeHierarchies.get(path.resolve(filePath)); if (!hierarchy) { return { totalScopes: 0, averageScopeDepth: 0, maxScopeDepth: 0, scopeComplexity: new Map(), variableDensity: new Map(), shadowingRate: 0, unusedVariableRate: 0 }; } const totalScopes = hierarchy.allScopes.size; const depths: number[] = []; const scopeComplexity = new Map<string, number>(); const variableDensity = new Map<string, number>(); let totalVariables = 0; let shadowingCount = 0; let unusedCount = 0; for (const [scopeId, scope] of hierarchy.allScopes) { const depth = this.calculateScopeDepth(scope); depths.push(depth); const complexity = this.calculateScopeComplexity(scope); scopeComplexity.set(scopeId, complexity); const density = scope.variables.size; variableDensity.set(scopeId, density); totalVariables += density; // Count shadowing and unused variables for (const [, variable] of scope.variables) { if (variable.shadows) shadowingCount++; if (variable.usages.length === 0) unusedCount++; } } const averageScopeDepth = depths.length > 0 ? depths.reduce((a, b) => a + b, 0) / depths.length : 0; const maxScopeDepth = depths.length > 0 ? Math.max(...depths) : 0; const shadowingRate = totalVariables > 0 ? shadowingCount / totalVariables : 0; const unusedVariableRate = totalVariables > 0 ? unusedCount / totalVariables : 0; return { totalScopes, averageScopeDepth, maxScopeDepth, scopeComplexity, variableDensity, shadowingRate, unusedVariableRate }; } /** * Generate scope analysis report */ generateScopeReport(filePath: string): { metrics: ScopeMetrics; analysis: ScopeAnalysisResult | null; recommendations: string[]; } { const absolutePath = path.resolve(filePath); const metrics = this.calculateScopeMetrics(absolutePath); const analysis = this.scopeHierarchies.has(absolutePath) ? this.getAnalysisResult(absolutePath) : null; const recommendations = this.generateRecommendations(metrics, analysis); return { metrics, analysis, recommendations }; } private async buildScopeHierarchy(filePath: string, context: SemanticContext): Promise<ScopeHierarchy> { const root = this.createScopeNode('global', 'global', 0, Number.MAX_SAFE_INTEGER, null, filePath); const allScopes = new Map<string, ScopeNode>(); const scopesByType = new Map<ScopeNode['type'], ScopeNode[]>(); const variableResolution = new Map<string, VariableInfo[]>(); allScopes.set(root.id, root); this.addToScopesByType(scopesByType, root); // Build scope hierarchy from semantic context await this.buildScopeTree(context, root, allScopes, scopesByType); // Resolve variable accessibility this.resolveVariableAccessibility(root); // Build variable resolution map this.buildVariableResolution(allScopes, variableResolution); return { root, allScopes, scopesByType, variableResolution }; } private async buildScopeTree( context: SemanticContext, parentScope: ScopeNode, allScopes: Map<string, ScopeNode>, scopesByType: Map<ScopeNode['type'], ScopeNode[]> ): Promise<void> { // Create scopes for each symbol that defines a scope for (const [symbol, symbolInfo] of context.symbols) { const scopeType = this.getScopeTypeFromSymbol(symbolInfo); if (scopeType && symbolInfo.startLine && symbolInfo.endLine) { const scope = this.createScopeNode( symbol, scopeType, symbolInfo.startLine, symbolInfo.endLine, parentScope, parentScope.file ); // Add symbol to the scope scope.symbols.set(symbol, symbolInfo); // Create variable info if it's a variable-like symbol if (this.isVariableSymbol(symbolInfo)) { const variableInfo = this.createVariableInfo(symbol, symbolInfo, scope.id); scope.variables.set(symbol, variableInfo); } parentScope.children.push(scope); allScopes.set(scope.id, scope); this.addToScopesByType(scopesByType, scope); // Recursively build child scopes await this.buildScopeTree(context, scope, allScopes, scopesByType); } else { // Add symbol to parent scope parentScope.symbols.set(symbol, symbolInfo); if (this.isVariableSymbol(symbolInfo)) { const variableInfo = this.createVariableInfo(symbol, symbolInfo, parentScope.id); parentScope.variables.set(symbol, variableInfo); } } } } private createScopeNode( name: string, type: ScopeNode['type'], startLine: number, endLine: number, parent: ScopeNode | null, file: string ): ScopeNode { return { id: `${file}:${type}:${name}:${++this.globalScopeId}`, name, type, startLine, endLine, parent, children: [], symbols: new Map(), variables: new Map(), accessibleSymbols: new Set(), file }; } private createVariableInfo(symbol: string, symbolInfo: SymbolInfo, scopeId: string): VariableInfo { return { name: symbol, type: symbolInfo.type, declarationLine: symbolInfo.startLine || 0, scope: scopeId, isParameter: this.isParameter(symbolInfo), isConstant: this.isConstant(symbolInfo), isHoisted: this.isHoisted(symbolInfo), shadowedBy: [], shadows: null, usages: [] }; } private getScopeTypeFromSymbol(symbolInfo: SymbolInfo): ScopeNode['type'] | null { switch (symbolInfo.type) { case 'function': return 'function'; case 'class': return 'class'; case 'module': return 'module'; default: return null; } } private isVariableSymbol(symbolInfo: SymbolInfo): boolean { return ['variable', 'parameter', 'constant'].includes(symbolInfo.type); } private isParameter(symbolInfo: SymbolInfo): boolean { return symbolInfo.type === 'parameter'; } private isConstant(symbolInfo: SymbolInfo): boolean { return symbolInfo.type === 'constant' || symbolInfo.name?.includes('const') === true; } private isHoisted(symbolInfo: SymbolInfo): boolean { // In JavaScript, var declarations and function declarations are hoisted return symbolInfo.type === 'function' || symbolInfo.name?.includes('var') === true; } private addToScopesByType(scopesByType: Map<ScopeNode['type'], ScopeNode[]>, scope: ScopeNode): void { if (!scopesByType.has(scope.type)) { scopesByType.set(scope.type, []); } scopesByType.get(scope.type)!.push(scope); } private resolveVariableAccessibility(scope: ScopeNode): void { // Add all symbols from current scope for (const symbol of scope.symbols.keys()) { scope.accessibleSymbols.add(symbol); } // Add symbols from parent scopes let parentScope = scope.parent; while (parentScope) { for (const symbol of parentScope.symbols.keys()) { scope.accessibleSymbols.add(symbol); } parentScope = parentScope.parent; } // Recursively resolve for children for (const child of scope.children) { this.resolveVariableAccessibility(child); } } private buildVariableResolution( allScopes: Map<string, ScopeNode>, variableResolution: Map<string, VariableInfo[]> ): void { for (const [, scope] of allScopes) { for (const [variableName, variableInfo] of scope.variables) { if (!variableResolution.has(variableName)) { variableResolution.set(variableName, []); } variableResolution.get(variableName)!.push(variableInfo); } } // Detect shadowing for (const [variableName, variables] of variableResolution) { if (variables.length > 1) { // Sort by scope depth (deeper scopes shadow shallower ones) variables.sort((a, b) => { const scopeA = allScopes.get(a.scope)!; const scopeB = allScopes.get(b.scope)!; return this.calculateScopeDepth(scopeB) - this.calculateScopeDepth(scopeA); }); // Set up shadowing relationships for (let i = 0; i < variables.length - 1; i++) { const shadowing = variables[i]; const shadowed = variables[i + 1]; shadowing.shadows = shadowed.name; shadowed.shadowedBy.push(shadowing.name); } } } } private detectShadowingIssues(hierarchy: ScopeHierarchy): ShadowingIssue[] { const issues: ShadowingIssue[] = []; for (const [, variables] of hierarchy.variableResolution) { if (variables.length > 1) { for (const variable of variables) { if (variable.shadows) { const shadowedVariable = variables.find(v => v.name === variable.shadows); if (shadowedVariable) { issues.push({ shadowingVariable: variable.name, shadowedVariable: shadowedVariable.name, shadowingScope: variable.scope, shadowedScope: shadowedVariable.scope, line: variable.declarationLine, severity: this.getShadowingSeverity(variable, shadowedVariable), suggestion: this.getShadowingSuggestion(variable, shadowedVariable) }); } } } } } return issues; } private findUnusedVariables(hierarchy: ScopeHierarchy): UnusedVariable[] { const unused: UnusedVariable[] = []; for (const [, scope] of hierarchy.allScopes) { for (const [, variable] of scope.variables) { const reason = this.getUnusedReason(variable); if (reason) { unused.push({ name: variable.name, scope: variable.scope, declarationLine: variable.declarationLine, type: variable.type, reason }); } } } return unused; } private detectScopeViolations(hierarchy: ScopeHierarchy): ScopeViolation[] { const violations: ScopeViolation[] = []; // This would require more detailed AST analysis to detect actual violations // For now, return empty array as placeholder return violations; } private analyzeClosures(hierarchy: ScopeHierarchy): ClosureAnalysis[] { const closures: ClosureAnalysis[] = []; const functionScopes = hierarchy.scopesByType.get('function') || []; for (const functionScope of functionScopes) { const capturedVariables: ClosureAnalysis['capturedVariables'] = []; const potentialMemoryLeaks: string[] = []; // Find variables captured from outer scopes for (const symbol of functionScope.accessibleSymbols) { if (!functionScope.symbols.has(symbol)) { // This symbol is from an outer scope const outerScope = this.findSymbolScope(symbol, functionScope.parent); if (outerScope) { capturedVariables.push({ name: symbol, fromScope: outerScope.id, captureType: 'reference' // Simplified - would need more analysis }); // Check for potential memory leaks if (this.isPotentialMemoryLeak(symbol, outerScope)) { potentialMemoryLeaks.push(symbol); } } } } if (capturedVariables.length > 0) { closures.push({ functionName: functionScope.name, scope: functionScope.id, capturedVariables, potentialMemoryLeaks, recommendations: this.generateClosureRecommendations(capturedVariables, potentialMemoryLeaks) }); } } return closures; } private analyzeVariableLifetime(hierarchy: ScopeHierarchy): Map<string, VariableLifetime> { const lifetimes = new Map<string, VariableLifetime>(); for (const [, scope] of hierarchy.allScopes) { for (const [, variable] of scope.variables) { const firstUsage = variable.usages.length > 0 ? Math.min(...variable.usages.map(u => u.line)) : variable.declarationLine; const lastUsage = variable.usages.length > 0 ? Math.max(...variable.usages.map(u => u.line)) : variable.declarationLine; const lifespan = lastUsage - variable.declarationLine; const isLongLived = lifespan > 50; // Arbitrary threshold lifetimes.set(`${variable.scope}:${variable.name}`, { name: variable.name, scope: variable.scope, declarationLine: variable.declarationLine, firstUsage, lastUsage, totalUsages: variable.usages.length, lifespan, isLongLived }); } } return lifetimes; } private calculateScopeDepth(scope: ScopeNode): number { let depth = 0; let current = scope.parent; while (current) { depth++; current = current.parent; } return depth; } private calculateScopeComplexity(scope: ScopeNode): number { // Simple complexity based on number of symbols and child scopes const symbolCount = scope.symbols.size; const childCount = scope.children.length; const lineCount = scope.endLine - scope.startLine; return (symbolCount * 0.3) + (childCount * 0.4) + (lineCount * 0.001); } private getShadowingSeverity(shadowing: VariableInfo, shadowed: VariableInfo): ShadowingIssue['severity'] { // Parameters shadowing outer variables are usually warnings if (shadowing.isParameter) return 'warning'; // Constants shadowing variables might be errors if (shadowing.isConstant && !shadowed.isConstant) return 'error'; return 'warning'; } private getShadowingSuggestion(shadowing: VariableInfo, shadowed: VariableInfo): string { if (shadowing.isParameter) { return `Consider renaming parameter '${shadowing.name}' to avoid shadowing outer variable`; } return `Consider renaming '${shadowing.name}' to avoid confusion with outer scope variable`; } private getUnusedReason(variable: VariableInfo): UnusedVariable['reason'] | null { if (variable.usages.length === 0) { return 'never_used'; } const hasReads = variable.usages.some(u => u.type === 'read' || u.type === 'readwrite'); if (!hasReads) { return 'write_only'; } // Could add more sophisticated analysis for dead_after_assignment return null; } private findSymbolScope(symbol: string, startScope: ScopeNode | null): ScopeNode | null { let current = startScope; while (current) { if (current.symbols.has(symbol)) { return current; } current = current.parent; } return null; } private isPotentialMemoryLeak(symbol: string, scope: ScopeNode): boolean { // Simplified check - in practice would need more sophisticated analysis return scope.type === 'global' || symbol.includes('cache') || symbol.includes('store'); } private generateClosureRecommendations( capturedVariables: ClosureAnalysis['capturedVariables'], potentialMemoryLeaks: string[] ): string[] { const recommendations: string[] = []; if (capturedVariables.length > 5) { recommendations.push('Consider reducing the number of captured variables'); } if (potentialMemoryLeaks.length > 0) { recommendations.push('Review captured variables for potential memory leaks'); } return recommendations; } private generateRecommendations(metrics: ScopeMetrics, analysis: ScopeAnalysisResult | null): string[] { const recommendations: string[] = []; if (metrics.maxScopeDepth > 6) { recommendations.push('Consider reducing scope nesting depth for better readability'); } if (metrics.shadowingRate > 0.1) { recommendations.push('High variable shadowing rate - consider renaming variables'); } if (metrics.unusedVariableRate > 0.2) { recommendations.push('High unused variable rate - consider cleanup'); } if (analysis) { if (analysis.closureAnalysis.length > 0) { recommendations.push('Review closures for potential memory leaks'); } if (analysis.scopeViolations.length > 0) { recommendations.push('Fix scope violations to prevent runtime errors'); } } return recommendations; } private getAnalysisResult(filePath: string): ScopeAnalysisResult | null { // This would return the cached analysis result // For now, return null as placeholder return null; } /** * Clear all cached data */ clearCache(): void { this.scopeHierarchies.clear(); this.semanticAnalyzer.clearContexts(); } /** * Get scope hierarchy for a file */ getScopeHierarchy(filePath: string): ScopeHierarchy | null { return this.scopeHierarchies.get(path.resolve(filePath)) || null; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/FosterG4/mcpsaver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server