From 994effe6cf1876016935b87396cb8427dfd13e04 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:18 -0300 Subject: [PATCH 01/17] duplicate code detector --- spice/analyzers/duplicate_code_detection.py | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 spice/analyzers/duplicate_code_detection.py diff --git a/spice/analyzers/duplicate_code_detection.py b/spice/analyzers/duplicate_code_detection.py new file mode 100644 index 0000000..d35b9c3 --- /dev/null +++ b/spice/analyzers/duplicate_code_detection.py @@ -0,0 +1,108 @@ +# spice/analyzers/duplicate_code_detection.py +import hashlib +import re +from collections import defaultdict + +def detect_duplicate_code(file_path, min_lines=3): + """Detect duplicate code blocks in a file. + + Args: + file_path (str): Path to the file to analyze + min_lines (int): Minimum number of lines to consider as a block + + Returns: + dict: Contains duplicate_blocks_count, total_duplicate_lines, and duplicate_percentage + """ + with open(file_path, 'r', encoding='utf-8') as f: + code = f.read() + + lines = code.split('\n') + + # Normalize lines by removing comments and extra whitespace + normalized_lines = [] + for line in lines: + normalized = _normalize_line(line) + normalized_lines.append(normalized) + + # Generate all possible blocks of min_lines or more + block_hashes = defaultdict(list) + total_lines = len(normalized_lines) + + # Check blocks of different sizes + for block_size in range(min_lines, min(total_lines + 1, 20)): # Limit to reasonable block sizes + for i in range(total_lines - block_size + 1): + block = normalized_lines[i:i + block_size] + # Skip blocks that are mostly empty + if sum(1 for line in block if line.strip()) < block_size // 2: + continue + + block_text = '\n'.join(block) + block_hash = hashlib.md5(block_text.encode()).hexdigest() + block_hashes[block_hash].append({ + 'start_line': i + 1, + 'end_line': i + block_size, + 'size': block_size, + 'content': block_text + }) + + # Find duplicates (blocks that appear more than once) + duplicates = {} + total_duplicate_lines = 0 + processed_lines = set() + + for block_hash, occurrences in block_hashes.items(): + if len(occurrences) > 1: + # Choose the largest block size for this hash + largest_block = max(occurrences, key=lambda x: x['size']) + duplicates[block_hash] = { + 'occurrences': len(occurrences), + 'locations': occurrences, + 'size': largest_block['size'] + } + + # Count unique duplicate lines (avoid double counting overlapping blocks) + for occurrence in occurrences: + for line_num in range(occurrence['start_line'], occurrence['end_line'] + 1): + if line_num not in processed_lines: + processed_lines.add(line_num) + total_duplicate_lines += 1 + + duplicate_percentage = (total_duplicate_lines / max(total_lines, 1)) * 100 + + return { + 'duplicate_blocks_count': len(duplicates), + 'total_duplicate_lines': total_duplicate_lines, + 'duplicate_percentage': round(duplicate_percentage, 2), + 'details': duplicates + } + +def _normalize_line(line): + """Normalize a line of code for comparison by removing comments and standardizing whitespace.""" + # Remove comments (simplified approach) + line = re.sub(r'//.*$', '', line) # JS/Go style comments + line = re.sub(r'#.*$', '', line) # Python/Ruby style comments + + # Remove string literals (simplified) + line = re.sub(r'"[^"]*"', '""', line) + line = re.sub(r"'[^']*'", "''", line) + + # Normalize whitespace + line = re.sub(r'\s+', ' ', line.strip()) + + return line + +def get_duplicate_code_summary(file_path): + """Get a summary of duplicate code detection for integration with main analyzer. + + Args: + file_path (str): Path to the file to analyze + + Returns: + dict: Summary of duplicate code analysis + """ + result = detect_duplicate_code(file_path) + return { + 'duplicate_blocks': result['duplicate_blocks_count'], + 'duplicate_lines': result['total_duplicate_lines'], + 'duplicate_percentage': result['duplicate_percentage'] + } \ No newline at end of file From eea2014fac1326d6ac0f63b5afb0b9363b2869f7 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:25 -0300 Subject: [PATCH 02/17] avg func size analyzer --- spice/analyzers/average_function_size.py | 162 +++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 spice/analyzers/average_function_size.py diff --git a/spice/analyzers/average_function_size.py b/spice/analyzers/average_function_size.py new file mode 100644 index 0000000..8543d8e --- /dev/null +++ b/spice/analyzers/average_function_size.py @@ -0,0 +1,162 @@ +# spice/analyzers/average_function_size.py +import os +import re +from utils.get_lexer import get_lexer_for_file + +def calculate_average_function_size(file_path): + """Calculate the average size (in lines) of functions in a file. + + Args: + file_path (str): Path to the file to analyze + + Returns: + float: Average number of lines per function, or 0 if no functions found + """ + with open(file_path, 'r', encoding='utf-8') as f: + code = f.read() + + _, ext = os.path.splitext(file_path) + + if ext == '.py': + return _analyze_python_functions(code) + elif ext == '.js': + return _analyze_javascript_functions(code) + elif ext == '.rb': + return _analyze_ruby_functions(code) + elif ext == '.go': + return _analyze_go_functions(code) + else: + return 0.0 + +def _analyze_python_functions(code): + """Analyze Python functions and calculate average size.""" + lines = code.split('\n') + functions = [] + + for i, line in enumerate(lines): + stripped = line.strip() + # Find function definitions + if stripped.startswith('def ') and '(' in line: + start_line = i + # Find the indentation level of the function + func_indent = len(line) - len(line.lstrip()) + + # Find the end of the function + end_line = len(lines) + for j in range(i + 1, len(lines)): + current_line = lines[j] + if current_line.strip() == '': + continue + current_indent = len(current_line) - len(current_line.lstrip()) + # Function ends when we find a line with same or less indentation + if current_indent <= func_indent and current_line.strip(): + end_line = j + break + + function_size = end_line - start_line + functions.append(function_size) + + return sum(functions) / len(functions) if functions else 0.0 + +def _analyze_javascript_functions(code): + """Analyze JavaScript functions and calculate average size.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + + # Traditional function declarations + if re.match(r'function\s+\w+\s*\(', line) or re.match(r'function\s*\(', line): + start_line = i + brace_count = line.count('{') - line.count('}') + + # Find the closing brace + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + + function_size = j - start_line + functions.append(function_size) + i = j + # Arrow functions + elif '=>' in line: + start_line = i + if '{' in line: + brace_count = line.count('{') - line.count('}') + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + function_size = j - start_line + else: + function_size = 1 # Single line arrow function + functions.append(function_size) + i += 1 + else: + i += 1 + + return sum(functions) / len(functions) if functions else 0.0 + +def _analyze_ruby_functions(code): + """Analyze Ruby functions and calculate average size.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + + # Method definitions + if re.match(r'def\s+\w+', line): + start_line = i + # Find the corresponding 'end' + end_keywords = 1 + j = i + 1 + + while j < len(lines) and end_keywords > 0: + current_line = lines[j].strip() + # Count def, class, module, if, while, etc. that need 'end' + if re.match(r'(def|class|module|if|unless|while|until|for|begin|case)\s', current_line): + end_keywords += 1 + elif current_line == 'end': + end_keywords -= 1 + j += 1 + + function_size = j - start_line + functions.append(function_size) + i = j + else: + i += 1 + + return sum(functions) / len(functions) if functions else 0.0 + +def _analyze_go_functions(code): + """Analyze Go functions and calculate average size.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + + # Function declarations + if re.match(r'func\s+(\w+\s*)?\(', line) or re.match(r'func\s*\([^)]*\)\s*\w+\s*\(', line): + start_line = i + brace_count = line.count('{') - line.count('}') + + # Find the closing brace + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + + function_size = j - start_line + functions.append(function_size) + i = j + else: + i += 1 + + return sum(functions) / len(functions) if functions else 0.0 \ No newline at end of file From 7b8c6e5810047e4aef390d319f8f0981f9551128 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:33 -0300 Subject: [PATCH 03/17] asymptototici analyzer --- spice/analyzers/asymptotic_complexity.py | 306 +++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 spice/analyzers/asymptotic_complexity.py diff --git a/spice/analyzers/asymptotic_complexity.py b/spice/analyzers/asymptotic_complexity.py new file mode 100644 index 0000000..26a7638 --- /dev/null +++ b/spice/analyzers/asymptotic_complexity.py @@ -0,0 +1,306 @@ +# spice/analyzers/asymptotic_complexity.py +import os +import re +from collections import defaultdict + +def analyze_asymptotic_complexity(file_path): + """Analyze the asymptotic complexity of functions in a file. + + Args: + file_path (str): Path to the file to analyze + + Returns: + dict: Contains complexity analysis results + """ + with open(file_path, 'r', encoding='utf-8') as f: + code = f.read() + + _, ext = os.path.splitext(file_path) + + if ext == '.py': + return _analyze_python_complexity(code) + elif ext == '.js': + return _analyze_javascript_complexity(code) + elif ext == '.rb': + return _analyze_ruby_complexity(code) + elif ext == '.go': + return _analyze_go_complexity(code) + else: + return {'average_complexity': 'O(1)', 'complexity_distribution': {}} + +def _analyze_python_complexity(code): + """Analyze complexity of Python functions.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + if line.startswith('def ') and '(' in line: + func_name = re.search(r'def\s+(\w+)', line).group(1) + start_line = i + func_indent = len(lines[i]) - len(lines[i].lstrip()) + + # Find function end + end_line = len(lines) + for j in range(i + 1, len(lines)): + if lines[j].strip() == '': + continue + current_indent = len(lines[j]) - len(lines[j].lstrip()) + if current_indent <= func_indent and lines[j].strip(): + end_line = j + break + + func_code = '\n'.join(lines[start_line:end_line]) + complexity = _calculate_complexity(func_code) + functions.append({ + 'name': func_name, + 'complexity': complexity, + 'start_line': start_line + 1, + 'end_line': end_line + }) + i = end_line + else: + i += 1 + + return _summarize_complexity(functions) + +def _analyze_javascript_complexity(code): + """Analyze complexity of JavaScript functions.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + + # Traditional functions + func_match = re.search(r'function\s+(\w+)\s*\(', line) + if func_match: + func_name = func_match.group(1) + start_line = i + brace_count = line.count('{') - line.count('}') + + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + + func_code = '\n'.join(lines[start_line:j]) + complexity = _calculate_complexity(func_code) + functions.append({ + 'name': func_name, + 'complexity': complexity, + 'start_line': start_line + 1, + 'end_line': j + }) + i = j + # Arrow functions + elif '=>' in line: + arrow_match = re.search(r'(\w+)\s*=.*=>', line) + if arrow_match: + func_name = arrow_match.group(1) + start_line = i + + if '{' in line: + brace_count = line.count('{') - line.count('}') + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + end_line = j + else: + end_line = i + 1 + + func_code = '\n'.join(lines[start_line:end_line]) + complexity = _calculate_complexity(func_code) + functions.append({ + 'name': func_name, + 'complexity': complexity, + 'start_line': start_line + 1, + 'end_line': end_line + }) + i += 1 + else: + i += 1 + + return _summarize_complexity(functions) + +def _analyze_ruby_complexity(code): + """Analyze complexity of Ruby functions.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + func_match = re.search(r'def\s+(\w+)', line) + + if func_match: + func_name = func_match.group(1) + start_line = i + end_keywords = 1 + j = i + 1 + + while j < len(lines) and end_keywords > 0: + current_line = lines[j].strip() + if re.match(r'(def|class|module|if|unless|while|until|for|begin|case)\s', current_line): + end_keywords += 1 + elif current_line == 'end': + end_keywords -= 1 + j += 1 + + func_code = '\n'.join(lines[start_line:j]) + complexity = _calculate_complexity(func_code) + functions.append({ + 'name': func_name, + 'complexity': complexity, + 'start_line': start_line + 1, + 'end_line': j + }) + i = j + else: + i += 1 + + return _summarize_complexity(functions) + +def _analyze_go_complexity(code): + """Analyze complexity of Go functions.""" + lines = code.split('\n') + functions = [] + + i = 0 + while i < len(lines): + line = lines[i].strip() + func_match = re.search(r'func\s+(\w+)\s*\(', line) + + if func_match: + func_name = func_match.group(1) + start_line = i + brace_count = line.count('{') - line.count('}') + + j = i + 1 + while j < len(lines) and brace_count > 0: + brace_count += lines[j].count('{') - lines[j].count('}') + j += 1 + + func_code = '\n'.join(lines[start_line:j]) + complexity = _calculate_complexity(func_code) + functions.append({ + 'name': func_name, + 'complexity': complexity, + 'start_line': start_line + 1, + 'end_line': j + }) + i = j + else: + i += 1 + + return _summarize_complexity(functions) + +def _calculate_complexity(code): + """Calculate the asymptotic complexity of a code block.""" + complexity_score = 1 # Base complexity O(1) + + # Count nested loops and conditionals + loop_patterns = [ + r'\bfor\b', # for loops + r'\bwhile\b', # while loops + r'\bforeach\b', # foreach (Ruby) + r'\.each\b', # .each (Ruby) + r'\.map\b', # .map + r'\.filter\b', # .filter + r'\.reduce\b' # .reduce + ] + + conditional_patterns = [ + r'\bif\b', + r'\belse\b', + r'\belif\b', + r'\bunless\b', # Ruby + r'\bcase\b', + r'\bswitch\b' + ] + + recursive_patterns = [ + r'\breturn\s+\w+\(', # potential recursion + ] + + # Count nesting levels + nesting_level = 0 + max_nesting = 0 + lines = code.split('\n') + + for line in lines: + stripped = line.strip() + + # Check for loop patterns + for pattern in loop_patterns: + if re.search(pattern, stripped): + complexity_score *= 2 # Each loop adds a factor + nesting_level += 1 + break + + # Check for conditional patterns (less impact than loops) + for pattern in conditional_patterns: + if re.search(pattern, stripped): + complexity_score += 1 + break + + # Check for recursion + for pattern in recursive_patterns: + if re.search(pattern, stripped): + complexity_score *= 3 # Recursion significantly increases complexity + break + + # Track nesting (simplified) + if any(char in stripped for char in ['{', 'do']): + nesting_level += 1 + if any(char in stripped for char in ['}', 'end']): + nesting_level = max(0, nesting_level - 1) + + max_nesting = max(max_nesting, nesting_level) + + # Apply nesting multiplier + if max_nesting > 2: + complexity_score *= max_nesting + + # Map score to Big O notation + if complexity_score <= 1: + return 'O(1)' + elif complexity_score <= 3: + return 'O(log n)' + elif complexity_score <= 10: + return 'O(n)' + elif complexity_score <= 25: + return 'O(n log n)' + elif complexity_score <= 50: + return 'O(n²)' + elif complexity_score <= 100: + return 'O(n³)' + else: + return 'O(2^n)' + +def _summarize_complexity(functions): + """Summarize complexity analysis results.""" + if not functions: + return { + 'average_complexity': 'O(1)', + 'complexity_distribution': {}, + 'total_functions': 0 + } + + # Count complexity distribution + complexity_counts = defaultdict(int) + for func in functions: + complexity_counts[func['complexity']] += 1 + + # Calculate "average" complexity (most common) + most_common_complexity = max(complexity_counts.items(), key=lambda x: x[1])[0] + + return { + 'average_complexity': most_common_complexity, + 'complexity_distribution': dict(complexity_counts), + 'total_functions': len(functions), + 'function_details': functions + } \ No newline at end of file From 65f5e5196a094c9b9e0fed30fddc952371223d96 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:37 -0300 Subject: [PATCH 04/17] add new analyzers --- spice/analyze.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/spice/analyze.py b/spice/analyze.py index ed8fd54..0b58d23 100644 --- a/spice/analyze.py +++ b/spice/analyze.py @@ -11,7 +11,9 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> file_path (str): Path to the file to analyze selected_stats (list, optional): List of stats to compute. If None, compute all stats. Valid stats are: "line_count", "function_count", "comment_line_count", - "inline_comment_count", "indentation_level" + "inline_comment_count", "indentation_level", "external_dependencies_count", + "method_type_count", "comment_ratio", "average_function_size", + "duplicate_code_detection", "asymptotic_complexity" Returns: dict: Dictionary containing the requested stats and file information @@ -34,8 +36,13 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> if not ext: raise ValueError("File has no extension") - # Define valid stats - valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count", "comment_ratio"] + # Define valid stats (including new ones) + valid_stats = [ + "line_count", "function_count", "comment_line_count", "inline_comment_count", + "indentation_level", "external_dependencies_count", "method_type_count", + "comment_ratio", "average_function_size", "duplicate_code_detection", + "asymptotic_complexity" + ] # default to all stats if none specified if selected_stats is None: @@ -110,8 +117,32 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> if "comment_ratio" in selected_stats: from spice.analyzers.count_comment_ratio import count_comment_ratio results["comment_ratio"] = count_comment_ratio(file_path) + + # NEW FEATURES BELOW + + # average function size if requested + if "average_function_size" in selected_stats: + from spice.analyzers.average_function_size import calculate_average_function_size + results["average_function_size"] = calculate_average_function_size(file_path) + + # duplicate code detection if requested + if "duplicate_code_detection" in selected_stats: + from spice.analyzers.duplicate_code_detection import get_duplicate_code_summary + duplicate_info = get_duplicate_code_summary(file_path) + results["duplicate_blocks"] = duplicate_info["duplicate_blocks"] + results["duplicate_lines"] = duplicate_info["duplicate_lines"] + results["duplicate_percentage"] = duplicate_info["duplicate_percentage"] + + # asymptotic complexity analysis if requested + if "asymptotic_complexity" in selected_stats: + from spice.analyzers.asymptotic_complexity import analyze_asymptotic_complexity + complexity_info = analyze_asymptotic_complexity(file_path) + results["average_complexity"] = complexity_info["average_complexity"] + results["complexity_distribution"] = complexity_info["complexity_distribution"] + results["total_analyzed_functions"] = complexity_info.get("total_functions", 0) + return results except Exception as e: # Add context to any errors that occur during analysis - raise Exception(f"Error analyzing file {file_path}: {str(e)}") + raise Exception(f"Error analyzing file {file_path}: {str(e)}") \ No newline at end of file From 8571da721d333b3a8fd72ac557d54060ff165994 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:44 -0300 Subject: [PATCH 05/17] add new analyzers to menu --- cli/commands/analyze.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/cli/commands/analyze.py b/cli/commands/analyze.py index 7d4dda9..a13362c 100644 --- a/cli/commands/analyze.py +++ b/cli/commands/analyze.py @@ -63,7 +63,7 @@ def analyze_command(file, all, json_output, LANG_FILE): # load translations messages = get_translation(LANG_FILE) - # define available stats + # define available stats (updated with new features) available_stats = [ "line_count", "function_count", @@ -73,9 +73,12 @@ def analyze_command(file, all, json_output, LANG_FILE): "external_dependencies_count", "method_type_count", "comment_ratio", + "average_function_size", + "duplicate_code_detection", + "asymptotic_complexity" ] - # dictionary for the stats + # dictionary for the stats (updated with new features) stats_labels = { "line_count": messages.get("line_count_option", "Line Count"), "function_count": messages.get("function_count_option", "Function Count"), @@ -87,6 +90,9 @@ def analyze_command(file, all, json_output, LANG_FILE): "private_methods_count": messages.get("private_methods_count_option", "Private Methods Count"), "public_methods_count": messages.get("public_methods_count_option", "Public Methods Count"), "comment_ratio": messages.get("comment_ratio_option", "Comment to Code Ratio"), + "average_function_size": messages.get("average_function_size_option", "Average Function Size"), + "duplicate_code_detection": messages.get("duplicate_code_detection_option", "Duplicate Code Detection"), + "asymptotic_complexity": messages.get("asymptotic_complexity_option", "Asymptotic Complexity Analysis") } # If --all flag is used, skip the selection menu and use all stats @@ -156,10 +162,26 @@ def analyze_command(file, all, json_output, LANG_FILE): print(f"{messages.get('private_methods_count_option', 'Private Methods Count')}: {mtc['private']}") continue - if stat == "indentation_level" and "indentation_type" in results: + elif stat == "indentation_level" and "indentation_type" in results: print(f"{messages.get('indentation_type', 'Indentation Type')}: {results.get('indentation_type', 'N/A')}") print(f"{messages.get('indentation_size', 'Indentation Size')}: {results.get('indentation_size', 'N/A')}") continue + + elif stat == "duplicate_code_detection" and any(key in results for key in ["duplicate_blocks", "duplicate_lines", "duplicate_percentage"]): + print(f"{messages.get('duplicate_blocks', 'Duplicate Blocks')}: {results.get('duplicate_blocks', 'N/A')}") + print(f"{messages.get('duplicate_lines', 'Duplicate Lines')}: {results.get('duplicate_lines', 'N/A')}") + print(f"{messages.get('duplicate_percentage', 'Duplicate Percentage')}: {results.get('duplicate_percentage', 'N/A')}%") + continue + + elif stat == "asymptotic_complexity" and any(key in results for key in ["average_complexity", "complexity_distribution", "total_analyzed_functions"]): + print(f"{messages.get('average_complexity', 'Average Complexity')}: {results.get('average_complexity', 'N/A')}") + print(f"{messages.get('total_analyzed_functions', 'Total Analyzed Functions')}: {results.get('total_analyzed_functions', 'N/A')}") + complexity_dist = results.get('complexity_distribution', {}) + if complexity_dist: + print(f"{messages.get('complexity_distribution', 'Complexity Distribution')}:") + for complexity, count in complexity_dist.items(): + print(f" {complexity}: {count}") + continue elif stat in results: print(f"{stats_labels[stat]}: {results[stat]}") From b4f76314151216050603fbaf5831efcfd277f4fb Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:14:49 -0300 Subject: [PATCH 06/17] translations for new analyzers --- cli/translations/en.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cli/translations/en.py b/cli/translations/en.py index 3b2c3d7..c0e25ea 100644 --- a/cli/translations/en.py +++ b/cli/translations/en.py @@ -31,4 +31,14 @@ "private_methods_count_option": "Private Methods Count", "public_methods_count_option": "Public Methods Count", "comment_ratio_option": "Comment to Code Ratio", + # new + "average_function_size_option": "Average Function Size", + "duplicate_code_detection_option": "Duplicate Code Detection", + "asymptotic_complexity_option": "Asymptotic Complexity Analysis", + "duplicate_blocks": "Duplicate Blocks", + "duplicate_lines": "Duplicate Lines", + "duplicate_percentage": "Duplicate Percentage", + "average_complexity": "Average Complexity", + "total_analyzed_functions": "Total Analyzed Functions", + "complexity_distribution": "Complexity Distribution" } \ No newline at end of file From fb185d8073e33f0730b7dec057274f5af4d6ce07 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:34:36 -0300 Subject: [PATCH 07/17] code complexity componeent --- .../pages/components/ComplexityCard.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 spicecloud/pages/components/ComplexityCard.tsx diff --git a/spicecloud/pages/components/ComplexityCard.tsx b/spicecloud/pages/components/ComplexityCard.tsx new file mode 100644 index 0000000..c45f5ad --- /dev/null +++ b/spicecloud/pages/components/ComplexityCard.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { styles } from '../utils/styles'; + +interface Props { + averageComplexity?: string; + distribution?: Record; + totalFunctions?: number; +} + +export const ComplexityCard: React.FC = ({ + averageComplexity = 'N/A', + distribution, + totalFunctions, +}) => { + return ( +
+
+

Complexity

+ {averageComplexity} +
+ +
+ {totalFunctions !== undefined && ( +

+ Functions analysed: {totalFunctions} +

+ )} + {distribution && + Object.entries(distribution).map(([bigO, qty]) => ( +

+ {bigO}: {qty} +

+ ))} +
+
+ ); +}; From 25b75282e3d726e5083f5f545d9918100aaf5eba Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:34:43 -0300 Subject: [PATCH 08/17] duplicate code componeent --- .../pages/components/DuplicateCodeCard.tsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 spicecloud/pages/components/DuplicateCodeCard.tsx diff --git a/spicecloud/pages/components/DuplicateCodeCard.tsx b/spicecloud/pages/components/DuplicateCodeCard.tsx new file mode 100644 index 0000000..a9ded7d --- /dev/null +++ b/spicecloud/pages/components/DuplicateCodeCard.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { styles } from '../utils/styles'; + +interface Props { + percentage?: number; + blocks?: number; + lines?: number; +} + +export const DuplicateCodeCard: React.FC = ({ + percentage = 0, + blocks, + lines, +}) => { + return ( +
+
+

Duplicate Code

+ {percentage.toFixed(2)}% +
+ +
+ {blocks !== undefined && ( +

Duplicate blocks: {blocks}

+ )} + {lines !== undefined && ( +

Duplicate lines: {lines}

+ )} +
+
+ ); +}; From 96e51a8183d91f29614c5e86faeb4a512b7fa0d2 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:35:16 -0300 Subject: [PATCH 09/17] update metrics grid componeent to include new metrics --- spicecloud/pages/components/MetricsGrid.tsx | 79 ++++++++++++++------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/spicecloud/pages/components/MetricsGrid.tsx b/spicecloud/pages/components/MetricsGrid.tsx index 1cbeda7..f3933dd 100644 --- a/spicecloud/pages/components/MetricsGrid.tsx +++ b/spicecloud/pages/components/MetricsGrid.tsx @@ -3,52 +3,79 @@ import { MetricData } from '../utils/types'; import { MetricCard } from './MetricCard'; import { MethodTypeCard } from './MethodTypeCard'; import { IndentationCard } from './IndentationCard'; +import { ComplexityCard } from './ComplexityCard'; +import { DuplicateCodeCard } from './DuplicateCodeCard'; import { styles } from '../utils/styles'; interface MetricsGridProps { selectedFile: MetricData; } +// keys handled by special cards ↓ +const SKIP_KEYS = [ + 'file_name', + 'file_path', + 'file_size', + 'file_extension', + 'indentation_type', + 'indentation_size', + 'method_type_count', + 'duplicate_blocks', + 'duplicate_lines', + 'duplicate_percentage', + 'average_complexity', + 'complexity_distribution', + 'total_analyzed_functions', +]; + export const MetricsGrid: React.FC = ({ selectedFile }) => { + const m = selectedFile.metrics; + return (
- {Object.entries(selectedFile.metrics).map(([key, value]) => { - // Skip file info metrics - if (['file_name', 'file_path', 'file_size', 'file_extension'].includes(key)) { - return null; - } + {/* generic & existing cards */} + {Object.entries(m).map(([key, value]) => { + if (SKIP_KEYS.includes(key)) return null; - // Handle method type count specially - if (key === 'method_type_count' && typeof value === 'object' && value !== null) { + if (key === 'method_type_count') { return ( - ); } - // Skip indentation keys as they'll be handled in their own card - if (key === 'indentation_type' || key === 'indentation_size') { - return null; - } - - return ( - - ); + return ; })} - {/* Indentation Card */} - {(selectedFile.metrics.indentation_type || selectedFile.metrics.indentation_size) && ( - + )} + + {/* duplicate‑code */} + {(m.duplicate_percentage !== undefined || + m.duplicate_blocks !== undefined || + m.duplicate_lines !== undefined) && ( + + )} + + {/* complexity */} + {m.average_complexity && ( + )}
); -}; \ No newline at end of file +}; From fb40eec7030af7772ff9216c0b6c05caf2c5e89e Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:35:23 -0300 Subject: [PATCH 10/17] new styles for new componeents --- spicecloud/pages/utils/styles.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/spicecloud/pages/utils/styles.ts b/spicecloud/pages/utils/styles.ts index 5c8c2f0..865a9a8 100644 --- a/spicecloud/pages/utils/styles.ts +++ b/spicecloud/pages/utils/styles.ts @@ -181,7 +181,8 @@ export const styles = { alignItems: 'center', gap: '0.75rem', marginBottom: '1rem', - color: '#000000' + color: '#000000', + cursor: 'pointer' }, metricIcon: { fontSize: '1.5rem', @@ -198,6 +199,19 @@ export const styles = { color: '#000000', marginBottom: '0.5rem' }, + metricDetails: { + /* corpo que aparece quando o card está aberto */ + marginTop: '0.5rem', + display: 'flex', + flexDirection: 'column' as const, + gap: '0.25rem' + }, + detailLine: { + /* linha individual dentro do corpo */ + fontSize: '0.875rem', + opacity: 0.8, // ligeiro cinza + color: '#000000' + }, progressBar: { width: '100%', height: '0.5rem', From 025bf1ebb0bcfc5ecdbf5305ed2d365cf85cfe0f Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:35:27 -0300 Subject: [PATCH 11/17] new types for new metrics --- spicecloud/pages/utils/types.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/spicecloud/pages/utils/types.ts b/spicecloud/pages/utils/types.ts index 86a681d..0808328 100644 --- a/spicecloud/pages/utils/types.ts +++ b/spicecloud/pages/utils/types.ts @@ -4,7 +4,29 @@ export interface MetricData { timestamp: number; file_name: string; file_path: string; - metrics: any; + metrics: { + file_extension: string; + line_count: number; + comment_line_count: number; + inline_comment_count: number; + indentation_type: string; + indentation_size: number; + function_count: number; + external_dependencies_count: number; + method_type_count: { private: number; public: number }; + comment_ratio: string; + average_function_size?: number; + + /* duplicate code */ + duplicate_blocks?: number; + duplicate_lines?: number; + duplicate_percentage?: number; + + /* complexity */ + average_complexity?: string; + complexity_distribution?: Record; + total_analyzed_functions?: number; + }; age: number; readable_timestamp: string; -} \ No newline at end of file +} From fea2a995249f4aec149fe7acb63f364e61713c21 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 12:45:52 -0300 Subject: [PATCH 12/17] concat avg func lines --- spicecloud/pages/components/MetricCard.tsx | 44 ++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/spicecloud/pages/components/MetricCard.tsx b/spicecloud/pages/components/MetricCard.tsx index 85f7bb4..bf40bed 100644 --- a/spicecloud/pages/components/MetricCard.tsx +++ b/spicecloud/pages/components/MetricCard.tsx @@ -1,5 +1,10 @@ import React from 'react'; -import { formatMetricValue, getMetricIcon, getMetricColor, formatLabel } from '../utils/utils'; +import { + formatMetricValue, + getMetricIcon, + getMetricColor, + formatLabel, +} from '../utils/utils'; import { styles } from '../utils/styles'; interface MetricCardProps { @@ -7,12 +12,22 @@ interface MetricCardProps { value: any; } -export const MetricCard: React.FC = ({ metricKey, value }) => { +export const MetricCard: React.FC = ({ + metricKey, + value, +}) => { + /* 👉 custom display for average_function_size */ + const displayValue = + metricKey === 'average_function_size' + ? `${parseFloat(value).toFixed(1)} lines` + : formatMetricValue(metricKey, value); + return ( -
{ @@ -26,23 +41,22 @@ export const MetricCard: React.FC = ({ metricKey, value }) => { {getMetricIcon(metricKey)} -

- {formatLabel(metricKey)} -

-
-
- {formatMetricValue(metricKey, value)} +

{formatLabel(metricKey)}

+ +
{displayValue}
+ + {/* progress bar only for ratio‑type metrics */} {metricKey.includes('ratio') && (
-
)} ); -}; \ No newline at end of file +}; From 290594e708c8e261d1a29bd3257d498f0948a8de Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 13:26:46 -0300 Subject: [PATCH 13/17] remove file size metric --- spicecloud/pages/components/FileHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spicecloud/pages/components/FileHeader.tsx b/spicecloud/pages/components/FileHeader.tsx index 352e17b..c86a6ff 100644 --- a/spicecloud/pages/components/FileHeader.tsx +++ b/spicecloud/pages/components/FileHeader.tsx @@ -26,7 +26,7 @@ export const FileHeader: React.FC = ({ selectedFile }) => {

🕒 {selectedFile.readable_timestamp} - 💾 {selectedFile.metrics.file_size ? `${(selectedFile.metrics.file_size / 1024).toFixed(1)} KB` : 'N/A'} + {/* 💾 {selectedFile.metrics.file_size ? `${(selectedFile.metrics.file_size / 1024).toFixed(1)} KB` : 'N/A'} */} 📂 {selectedFile.metrics.file_extension || 'N/A'}
From 3f0092c608e54a7dcc2bf8bb7f6123228d822292 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 13:38:30 -0300 Subject: [PATCH 14/17] spice cloud logo --- spicecloud/public/spicecloud-logo.png | Bin 0 -> 7040 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 spicecloud/public/spicecloud-logo.png diff --git a/spicecloud/public/spicecloud-logo.png b/spicecloud/public/spicecloud-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3a2f34710fddb50bd7d7d8cfb477bc146a84435f GIT binary patch literal 7040 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 g4blHE!!7%4T8uUCF_v?1ArTUGynhq literal 0 HcmV?d00001 From 622eedad616d58fe81d021d7f72a06711598f062 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 13:40:51 -0300 Subject: [PATCH 15/17] spicecloud logo with textx --- spicecloud/public/spicecloud-logo-text.png | Bin 0 -> 15569 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 spicecloud/public/spicecloud-logo-text.png diff --git a/spicecloud/public/spicecloud-logo-text.png b/spicecloud/public/spicecloud-logo-text.png new file mode 100644 index 0000000000000000000000000000000000000000..f03d28c5a21201c1d95c689140bee46e49a563ad GIT binary patch literal 15569 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 S4Sc5~1Ej?j#A-#1g8l~tUU~ii literal 0 HcmV?d00001 From 37a0e881c7859cf106128a00af4ed0761b3c3233 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 13:41:18 -0300 Subject: [PATCH 16/17] use spicecloud logo for favicon --- spicecloud/pages/_document.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spicecloud/pages/_document.tsx b/spicecloud/pages/_document.tsx index f0a5df0..0e650c7 100644 --- a/spicecloud/pages/_document.tsx +++ b/spicecloud/pages/_document.tsx @@ -4,7 +4,7 @@ export default function Document() { return ( - +
From 395a40d10419092557db15cdf34b246bdc5fa8d3 Mon Sep 17 00:00:00 2001 From: CodyKoInABox Date: Mon, 30 Jun 2025 13:41:45 -0300 Subject: [PATCH 17/17] use spice cloud logo on header --- spicecloud/pages/components/Header.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spicecloud/pages/components/Header.tsx b/spicecloud/pages/components/Header.tsx index 59d15f9..0e94f14 100644 --- a/spicecloud/pages/components/Header.tsx +++ b/spicecloud/pages/components/Header.tsx @@ -14,8 +14,8 @@ export const Header: React.FC = ({ dataLength, loading, onRefresh }

SpiceCode Logo SpiceCloud | Powered by SpiceCodeCLI