Skip to main content
Glama
intelligent_analysis.py27.6 kB
#!/usr/bin/env python3 """ Intelligent Kotlin Code Analysis Engine This module provides LSP - like intelligent analysis and refactoring capabilities for Kotlin code, including: - Semantic code analysis - Intelligent refactoring suggestions - Symbol resolution and navigation """ import re from dataclasses import dataclass from enum import Enum from typing import Any, Dict, List, Optional, Tuple class SymbolType(Enum): CLASS = "class" FUNCTION = "function" PROPERTY = "property" VARIABLE = "variable" PARAMETER = "parameter" IMPORT = "import" class RefactoringType(Enum): EXTRACT_METHOD = "extract_method" EXTRACT_CLASS = "extract_class" RENAME_SYMBOL = "rename_symbol" INLINE_FUNCTION = "inline_function" MOVE_CLASS = "move_class" EXTRACT_INTERFACE = "extract_interface" CONVERT_TO_DATA_CLASS = "convert_to_data_class" ADD_NULL_SAFETY = "add_null_safety" OPTIMIZE_IMPORTS = "optimize_imports" CONVERT_TO_COMPOSE = "convert_to_compose" @dataclass class CodeSymbol: name: str type: SymbolType line: int column: int end_line: int end_column: int scope: str modifiers: List[str] return_type: Optional[str] = None parameters: Optional[List[str]] = None usages: Optional[List[Tuple[int, int]]] = None def __post_init__(self) -> None: if self.parameters is None: self.parameters = [] if self.usages is None: self.usages = [] @dataclass class RefactoringAction: type: RefactoringType description: str confidence: float start_line: int end_line: int code_to_extract: str suggested_name: str parameters: List[str] return_type: Optional[str] preview: str impact_analysis: Dict[str, Any] @dataclass class CodeIssue: severity: str # error, warning, info message: str line: int column: int rule: str fix_suggestion: Optional[str] = None auto_fixable: bool = False class KotlinAnalyzer: """Advanced Kotlin code analyzer with LSP - like capabilities.""" def __init__(self) -> None: self.symbols: Dict[str, List[CodeSymbol]] = {} self.kotlin_keywords = { "abstract", "actual", "annotation", "as", "break", "by", "catch", "class", "companion", "const", "constructor", "continue", "crossinline", "data", "delegate", "do", "dynamic", "else", "enum", "expect", "external", "false", "field", "file", "final", "finally", "for", "fun", "get", "i", "import", "in", "infix", "init", "inline", "inner", "interface", "internal", "is", "lateinit", "noinline", "null", "object", "open", "operator", "out", "override", "package", "param", "private", "property", "protected", "public", "receiver", "reified", "return", "sealed", "set", "setparam", "super", "suspend", "tailrec", "this", "throw", "true", "try", "typealias", "typeo", "val", "var", "vararg", "when", "where", "while", } def analyze_file(self, file_path: str, content: str) -> Dict[str, Any]: """Perform comprehensive analysis of a Kotlin file.""" try: lines = content.split("\n") # Extract symbols symbols = self._extract_symbols(content, lines) # Detect code issues issues = self._detect_code_issues(content, lines) # Find refactoring opportunities refactoring_suggestions = self._suggest_refactorings(content, lines, symbols) # Calculate complexity metrics complexity = self._calculate_complexity(content, lines) # Analyze dependencies dependencies = self._analyze_dependencies(content) return { "file_path": file_path, "symbols": [self._symbol_to_dict(s) for s in symbols], "issues": [self._issue_to_dict(i) for i in issues], "refactoring_suggestions": [ self._refactoring_to_dict(r) for r in refactoring_suggestions ], "complexity_metrics": complexity, "dependencies": dependencies, "analysis_summary": { "total_lines": len(lines), "code_lines": len( [ line for line in lines if line.strip() and not line.strip().startswith("//") ] ), "comment_lines": len([line for line in lines if line.strip().startswith("//")]), "symbol_count": len(symbols), "issue_count": len(issues), "refactoring_opportunities": len(refactoring_suggestions), }, } except Exception as e: return {"error": "Analysis failed: {str(e)}"} def _extract_symbols(self, content: str, lines: List[str]) -> List[CodeSymbol]: """Extract code symbols (classes, functions, properties, etc.).""" symbols = [] # Pattern for class declarations class_pattern = r"^\s*((?:public|private|protected|internal)?\s*(?:abstract|final|open|sealed)?\s*(?:data|enum|annotation)?\s * class|interface|object)\s+(\w+)" # Pattern for function declarations function_pattern = r"^\s*((?:public|private|protected|internal)?\s*(?:override|open|final|abstract|suspend|inline|infix|operator)?\s * fun)\s+(\w+)\s*\(" # Pattern for property declarations property_pattern = r"^\s*((?:public|private|protected|internal)?\s*(?:override|open|final|abstract|const|lateinit)?\s*(?:val|var))\s+(\w+)" for i, line in enumerate(lines): # Extract classes class_match = re.search(class_pattern, line) if class_match: modifiers = class_match.group(1).split() name = class_match.group(2) symbols.append( CodeSymbol( name=name, type=SymbolType.CLASS, line=i + 1, column=class_match.start(2), end_line=i + 1, end_column=class_match.end(2), scope="file", modifiers=modifiers, ) ) # Extract functions function_match = re.search(function_pattern, line) if function_match: modifiers = function_match.group(1).split() name = function_match.group(2) # Extract parameters param_start = line.find("(", function_match.end()) param_end = line.find(")", param_start) if param_start != -1 and param_end != -1: param_str = line[param_start + 1 : param_end] parameters = [ p.strip().split(":")[0].strip() for p in param_str.split(",") if p.strip() ] else: parameters = [] # Extract return type return_type = None colon_pos = line.find(":", param_end if param_end != -1 else function_match.end()) if colon_pos != -1: brace_pos = line.find("{", colon_pos) eq_pos = line.find("=", colon_pos) end_pos = min(p for p in [brace_pos, eq_pos, len(line)] if p != -1) return_type = line[colon_pos + 1 : end_pos].strip() symbols.append( CodeSymbol( name=name, type=SymbolType.FUNCTION, line=i + 1, column=function_match.start(2), end_line=i + 1, end_column=function_match.end(2), scope="class" if any("class" in l for l in lines[:i]) else "file", modifiers=modifiers, parameters=parameters, return_type=return_type, ) ) # Extract properties property_match = re.search(property_pattern, line) if property_match and not function_match: # Avoid matching function parameters modifiers = property_match.group(1).split() name = property_match.group(2) symbols.append( CodeSymbol( name=name, type=SymbolType.PROPERTY, line=i + 1, column=property_match.start(2), end_line=i + 1, end_column=property_match.end(2), scope="class" if any("class" in l for l in lines[:i]) else "file", modifiers=modifiers, ) ) return symbols def _detect_code_issues(self, content: str, lines: List[str]) -> List[CodeIssue]: """Detect code quality issues and potential bugs.""" issues = [] for i, line in enumerate(lines): line_num = i + 1 stripped = line.strip() # Long lines if len(line) > 120: issues.append( CodeIssue( severity="warning", message="Line too long (>120 characters)", line=line_num, column=120, rule="line_length", fix_suggestion="Consider breaking this line into multiple lines", auto_fixable=False, ) ) # Non - null assertion operator if "!!" in line: issues.append( CodeIssue( severity="warning", message="Non - null assertion operator (!!) can cause NullPointerException", line=line_num, column=line.find("!!"), rule="null_safety", fix_suggestion="Consider using safe call operator (?.) or proper null check", auto_fixable=False, ) ) # Hardcoded strings if re.search(r'["\'][^"\']{20,}["\']', line) and "R.string" not in line: issues.append( CodeIssue( severity="info", message="Long hardcoded string should be extracted to resources", line=line_num, column=0, rule="hardcoded_strings", fix_suggestion="Extract to strings.xml resource file", auto_fixable=False, ) ) # Unused imports (simplified detection) if stripped.startswith("import ") and not stripped.startswith("import android."): import_name = stripped.split(".")[-1] if import_name not in content: issues.append( CodeIssue( severity="info", message="Unused import", line=line_num, column=0, rule="unused_imports", fix_suggestion="Remove unused import", auto_fixable=True, ) ) # Missing override annotation if ( re.search(r"fun\s+(?:equals|hashCode|toString)\s*\(", line) and "@Override" not in lines[i - 1] if i > 0 else False ): issues.append( CodeIssue( severity="warning", message="Missing @Override annotation", line=line_num, column=0, rule="missing_override", fix_suggestion="Add @Override annotation", auto_fixable=True, ) ) # Deprecated findViewById usage if "findViewById" in line and "kotlin - android - extensions" not in content: issues.append( CodeIssue( severity="warning", message="findViewById is deprecated, use View Binding instead", line=line_num, column=line.find("findViewById"), rule="deprecated_findviewbyid", fix_suggestion="Convert to View Binding or Data Binding", auto_fixable=False, ) ) # Inefficient string concatenation if "+" in line and ('"' in line or "'" in line) and "StringBuilder" not in line: string_concat = re.search(r'["\'][^"\']*["\']\s*\+', line) if string_concat: issues.append( CodeIssue( severity="info", message="Consider using string templates instead of concatenation", line=line_num, column=string_concat.start(), rule="string_concatenation", fix_suggestion='Use string templates: "text $variable"', auto_fixable=True, ) ) return issues def _suggest_refactorings( self, content: str, lines: List[str], symbols: List[CodeSymbol] ) -> List[RefactoringAction]: """Suggest intelligent refactoring actions.""" suggestions = [] # Find long methods that should be extracted for symbol in symbols: if symbol.type == SymbolType.FUNCTION: method_lines = self._get_method_lines(lines, symbol.line - 1) if len(method_lines) > 20: # Long method # Analyze method for extraction opportunities code_blocks = self._find_extractable_blocks(method_lines) for block in code_blocks: suggestions.append( RefactoringAction( type=RefactoringType.EXTRACT_METHOD, description="Extract code block from {symbol.name} into separate method", confidence=0.8, start_line=symbol.line + block["start"], end_line=symbol.line + block["end"], code_to_extract="\n".join( method_lines[block["start"] : block["end"]] ), suggested_name="extracted{block['start']}", parameters=block.get("variables", []), return_type=block.get("return_type"), preview=f"private fun extracted{block['start']}(): {block.get('return_type', 'Unit')} {{\n // extracted code\n}}", impact_analysis={ "complexity_reduction": len(method_lines) - block["end"] + block["start"] }, ) ) # Find classes that could be data classes for symbol in symbols: if symbol.type == SymbolType.CLASS and "data" not in symbol.modifiers: class_lines = self._get_class_lines(lines, symbol.line - 1) if self._is_data_class_candidate(class_lines): suggestions.append( RefactoringAction( type=RefactoringType.CONVERT_TO_DATA_CLASS, description="Convert {symbol.name} to data class", confidence=0.9, start_line=symbol.line, end_line=symbol.line, code_to_extract="", suggested_name=symbol.name, parameters=[], return_type=None, preview=f"data class {symbol.name}(...)", impact_analysis={ "auto_generated_methods": ["equals", "hashCode", "toString", "copy"] }, ) ) # Find null safety improvements nullable_patterns = re.finditer(r"(\w+)\?\.\w+", content) for match in nullable_patterns: line_num = content[: match.start()].count("\n") + 1 suggestions.append( RefactoringAction( type=RefactoringType.ADD_NULL_SAFETY, description="Add proper null safety check", confidence=0.7, start_line=line_num, end_line=line_num, code_to_extract=match.group(), suggested_name="", parameters=[], return_type=None, preview=f"{match.group(1)}?.let {{ /* safe operation */ }}", impact_analysis={"null_safety_improvement": True}, ) ) # Find Android View to Compose conversion opportunities if "findViewById" in content or "xml" in content.lower(): suggestions.append( RefactoringAction( type=RefactoringType.CONVERT_TO_COMPOSE, description="Convert XML layouts to Jetpack Compose", confidence=0.6, start_line=1, end_line=len(lines), code_to_extract="", suggested_name="", parameters=[], return_type=None, preview="@Composable\nfun YourComponent() {\n // Compose UI\n}", impact_analysis={"modernization": True, "performance_improvement": True}, ) ) return suggestions def _calculate_complexity(self, content: str, lines: List[str]) -> Dict[str, Any]: """Calculate code complexity metrics.""" # Cyclomatic complexity (simplified) complexity_keywords = ["i", "when", "while", "for", "catch", "&&", "||", "?:"] cyclomatic_complexity = 1 # Base complexity for keyword in complexity_keywords: cyclomatic_complexity += content.count(keyword) # Halstead metrics (simplified) operators = len(re.findall(r"[+\\\-*/=<>!&|%^]", content)) operands = len(re.findall(r"\b\w+\b", content)) # Maintainability index (simplified) lines_of_code = len([line for line in lines if line.strip()]) comment_ratio = len([line for line in lines if line.strip().startswith("//")]) / max( lines_of_code, 1 ) return { "cyclomatic_complexity": cyclomatic_complexity, "lines_of_code": lines_of_code, "comment_ratio": comment_ratio, "halstead_operators": operators, "halstead_operands": operands, "maintainability_score": max(0, 100 - cyclomatic_complexity * 2 + comment_ratio * 10), } def _analyze_dependencies(self, content: str) -> Dict[str, Any]: """Analyze imports and dependencies.""" import_lines = [ line.strip() for line in content.split("\n") if line.strip().startswith("import") ] android_imports = [imp for imp in import_lines if "android" in imp] kotlin_imports = [imp for imp in import_lines if "kotlin" in imp] third_party_imports = [ imp for imp in import_lines if not any(x in imp for x in ["android", "kotlin", "java."]) ] return { "total_imports": len(import_lines), "android_imports": len(android_imports), "kotlin_imports": len(kotlin_imports), "third_party_imports": len(third_party_imports), "import_list": import_lines, "dependency_analysis": { "heavy_dependencies": [ imp for imp in third_party_imports if any(heavy in imp for heavy in ["retrofit", "dagger", "rxjava"]) ], "compose_usage": any("compose" in imp.lower() for imp in import_lines), "coroutines_usage": any("coroutines" in imp.lower() for imp in import_lines), }, } def _get_method_lines(self, lines: List[str], start_line: int) -> List[str]: """Extract lines belonging to a method.""" method_lines = [] brace_count = 0 in_method = False for i in range(start_line, len(lines)): line = lines[i].strip() if "{" in line: in_method = True brace_count += line.count("{") if in_method: method_lines.append(lines[i]) brace_count += line.count("{") - line.count("}") if brace_count <= 0: break return method_lines def _get_class_lines(self, lines: List[str], start_line: int) -> List[str]: """Extract lines belonging to a class.""" return self._get_method_lines(lines, start_line) # Same logic for now def _find_extractable_blocks(self, method_lines: List[str]) -> List[Dict[str, Any]]: """Find code blocks that can be extracted into separate methods.""" blocks = [] current_block = {"start": 0, "variables": [], "return_type": "Unit"} for i, line in enumerate(method_lines): # Simple heuristic: consecutive lines doing similar operations if i > 0 and i % 5 == 0: # Every 5 lines could be a block current_block["end"] = i blocks.append(current_block.copy()) current_block = {"start": i, "variables": [], "return_type": "Unit"} return blocks[:3] # Limit suggestions def _is_data_class_candidate(self, class_lines: List[str]) -> bool: """Check if a class is a good candidate for conversion to data class.""" class_content = "\n".join(class_lines) # Has properties but no complex methods has_properties = any(re.search(r"val|var", line) for line in class_lines) has_simple_methods = class_content.count("fun") <= 2 no_inheritance = "extends" not in class_content and "override" not in class_content return has_properties and has_simple_methods and no_inheritance def _symbol_to_dict(self, symbol: CodeSymbol) -> Dict[str, Any]: """Convert CodeSymbol to dictionary.""" return { "name": symbol.name, "type": symbol.type.value, "line": symbol.line, "column": symbol.column, "end_line": symbol.end_line, "end_column": symbol.end_column, "scope": symbol.scope, "modifiers": symbol.modifiers, "return_type": symbol.return_type, "parameters": symbol.parameters, "usages": symbol.usages, } def _issue_to_dict(self, issue: CodeIssue) -> Dict[str, Any]: """Convert CodeIssue to dictionary.""" return { "severity": issue.severity, "message": issue.message, "line": issue.line, "column": issue.column, "rule": issue.rule, "fix_suggestion": issue.fix_suggestion, "auto_fixable": issue.auto_fixable, } def _refactoring_to_dict(self, refactoring: RefactoringAction) -> Dict[str, Any]: """Convert RefactoringAction to dictionary.""" return { "type": refactoring.type.value, "description": refactoring.description, "confidence": refactoring.confidence, "start_line": refactoring.start_line, "end_line": refactoring.end_line, "code_to_extract": refactoring.code_to_extract, "suggested_name": refactoring.suggested_name, "parameters": refactoring.parameters, "return_type": refactoring.return_type, "preview": refactoring.preview, "impact_analysis": refactoring.impact_analysis, } class IntelligentRefactoring: """Intelligent refactoring engine with context awareness.""" def __init__(self, project_path: str = "", security_manager: Any = None) -> None: self.project_path = project_path self.security_manager = security_manager self.analyzer = KotlinAnalyzer() def analyze_and_suggest( self, file_path: str, content: str, refactoring_request: Optional[str] = None ) -> Dict[str, Any]: """Analyze code and provide intelligent refactoring suggestions.""" analysis = self.analyzer.analyze_file(file_path, content) if refactoring_request: # Filter suggestions based on request filtered_suggestions = self._filter_suggestions( analysis.get("refactoring_suggestions", []), refactoring_request ) analysis["targeted_suggestions"] = filtered_suggestions return analysis def _filter_suggestions(self, suggestions: List[Dict], request: str) -> List[Dict]: """Filter refactoring suggestions based on user request.""" request_lower = request.lower() if "extract" in request_lower: return [s for s in suggestions if "extract" in s["type"]] elif "data class" in request_lower: return [s for s in suggestions if "data_class" in s["type"]] elif "compose" in request_lower: return [s for s in suggestions if "compose" in s["type"]] elif "null" in request_lower: return [s for s in suggestions if "null" in s["type"]] return suggestions

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/normaltusker/kotlin-mcp-server'

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