From 4df18154b41fc4d3afb94fa6225bd3b7b2d49f53 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 15 May 2025 20:28:34 -0300 Subject: [PATCH 1/5] feat: added number of external dependencies count --- cli/commands/analyze.py | 6 ++-- spice/analyze.py | 9 ++--- .../analyzers/count_external_dependencies.py | 36 +++++++++++++++++++ tests/sample-code/example.js | 14 ++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 spice/analyzers/count_external_dependencies.py diff --git a/cli/commands/analyze.py b/cli/commands/analyze.py index 595afa9..7181fc7 100644 --- a/cli/commands/analyze.py +++ b/cli/commands/analyze.py @@ -18,7 +18,8 @@ def analyze_command(file, all, json_output, LANG_FILE): "function_count", "comment_line_count", "inline_comment_count", - "indentation_level" + "indentation_level", + "external_dependencies_count" ] # dictionary for the stats @@ -27,7 +28,8 @@ def analyze_command(file, all, json_output, LANG_FILE): "function_count": messages.get("function_count_option", "Function Count"), "comment_line_count": messages.get("comment_line_count_option", "Comment Line Count"), "inline_comment_count": messages.get("inline_comment_count_option", "Inline Comment Count"), - "indentation_level": messages.get("indentation_level_option", "Indentation Analysis") + "indentation_level": messages.get("indentation_level_option", "Indentation Analysis"), + "external_dependencies_count": messages.get("external_dependencies_count_option", "External Dependencies Count") } # If --all flag is used, skip the selection menu and use all stats diff --git a/spice/analyze.py b/spice/analyze.py index ca40aa4..561d409 100644 --- a/spice/analyze.py +++ b/spice/analyze.py @@ -35,7 +35,7 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> raise ValueError("File has no extension") # Define valid stats - valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level"] + valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count"] # default to all stats if none specified if selected_stats is None: @@ -90,11 +90,12 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> # function count if requested if "function_count" in selected_stats: from spice.analyzers.count_functions import count_functions - from utils.get_lexer import get_lexer_for_file - LexerClass = get_lexer_for_file(file_path) - lexer = LexerClass(source_code=code) # Pass source_code explicitly results["function_count"] = count_functions(file_path) + # external dependencies count if requested + if "external_dependencies_count" in selected_stats: + from spice.analyzers.count_external_dependencies import count_external_dependencies + results["external_dependencies_count"] = count_external_dependencies(file_path) return results except Exception as e: diff --git a/spice/analyzers/count_external_dependencies.py b/spice/analyzers/count_external_dependencies.py new file mode 100644 index 0000000..6b717b1 --- /dev/null +++ b/spice/analyzers/count_external_dependencies.py @@ -0,0 +1,36 @@ +import os +import re + +def count_external_dependencies(path): + """Contar o número de dependências externas em um arquivo de exemplo.""" + _, ext = os.path.splitext(path) + with open(path, 'r') as file: + code = file.read() + + if ext == '.py': + # Contar o número de importações + pattern = r'^\s*(import\s+\w+|from\s+\w+\s+import\s+.+)' + matches = re.findall(pattern, code, flags=re.MULTILINE) + return len(matches) + elif ext == '.js': + # Contar o número de importações + require_pattern = r'require\s*\(\s*[\'"][^\'"]+[\'"]\s*\)' + import_pattern = r'^\s*import\s+.*from\s+[\'"][^\'"]+[\'"]' + matches = re.findall(require_pattern, code) + matches += re.findall(import_pattern, code, flags=re.MULTILINE) + return len(matches) + elif ext == ".rb": + pattern = r'^\s*require(_relative)?\s+[\'"][^\'"]+[\'"]' + matches = re.findall(pattern, code, flags=re.MULTILINE) + return len(matches) + elif ext == ".go": + import_block_pattern = r'import\s*\((.*?)\)' + single_import_pattern = r'^\s*import\s+[\'"][^\'"]+[\'"]' + block = re.findall(import_block_pattern, code, flags=re.DOTALL) + count = 0 + for b in block: + count += len(re.findall(r'[\'"][^\'"]+[\'"]', b)) + count += len(re.findall(single_import_pattern, code, flags=re.MULTILINE)) + return count + else: + return 0 \ No newline at end of file diff --git a/tests/sample-code/example.js b/tests/sample-code/example.js index bd5c9e3..d335f89 100644 --- a/tests/sample-code/example.js +++ b/tests/sample-code/example.js @@ -3,6 +3,20 @@ // This file demonstrates various Javascript language features, syntax, and constructs // for testing lexers, parsers, and code analyzers. // ============================================================================== +// Imports +require ('fs'); +require ('path'); +require ('util'); +require ('child_process'); +require ('http'); +require ('url'); +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { createServer } from 'http'; +import { parse } from 'url'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +// Total: 6 require, 6 imports // Constants const PI = Math.PI; From be79773f787d87db4a069b0aa9b555c7dde29016 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 15 May 2025 22:00:02 -0300 Subject: [PATCH 2/5] fix: found several bugs --- cli/commands/analyze.py | 4 +- cli/translations/en.py | 4 +- cli/translations/fremen.py | 4 +- cli/translations/pt-br.py | 4 +- spice/analyzers/identation.py | 30 ----- spice/analyzers/indentation_analysis.py | 47 ++++++++ tests/analyze/test_analyze_json_go.py | 112 +++++++++--------- tests/analyze/test_analyze_json_javascript.py | 8 +- tests/analyze/test_analyze_json_python.py | 112 +++++++++--------- tests/analyze/test_analyze_json_ruby.py | 112 +++++++++--------- .../test_count_external_dependencies.py | 19 +++ tests/analyze/test_identation_analysis.py | 22 ++++ tests/temp_test.js | 0 tests/temp_test.py | 2 + 14 files changed, 273 insertions(+), 207 deletions(-) delete mode 100644 spice/analyzers/identation.py create mode 100644 spice/analyzers/indentation_analysis.py create mode 100644 tests/analyze/test_count_external_dependencies.py create mode 100644 tests/analyze/test_identation_analysis.py create mode 100644 tests/temp_test.js create mode 100644 tests/temp_test.py diff --git a/cli/commands/analyze.py b/cli/commands/analyze.py index 7181fc7..e639437 100644 --- a/cli/commands/analyze.py +++ b/cli/commands/analyze.py @@ -71,7 +71,7 @@ def analyze_command(file, all, json_output, LANG_FILE): print(f"{messages.get('analyzing_file', 'Analyzing file')}: {file}") # get analysis results from analyze_file - results = analyze_file(file, selected_stats=selected_stat_keys) + analyze_file(file, selected_stat_keys) # output in JSON format if flag if json_output: @@ -84,7 +84,7 @@ def analyze_command(file, all, json_output, LANG_FILE): print(f"{messages.get('indentation_type', 'Indentation Type')}: {results['indentation_type']}") print(f"{messages.get('indentation_size', 'Indentation Size')}: {results['indentation_size']}") elif stat in results: - print(messages.get(stat, f"{stat.replace('_', ' ').title()}: {{count}}").format(count=results[stat])) + print(f"{stats_labels[stat]}: {results[stat]}") except Exception as e: if json_output: diff --git a/cli/translations/en.py b/cli/translations/en.py index 7c9885a..6e3f238 100644 --- a/cli/translations/en.py +++ b/cli/translations/en.py @@ -22,5 +22,7 @@ # keys for the version command "version_info": "SpiceCode Version:", "version_not_found": "Version information not found in setup.py", - "setup_not_found": "Error: setup.py not found." + "setup_not_found": "Error: setup.py not found.", + "indentation_level_option": "Indentation Level Analysis", + "external_dependencies_count_option": "External Dependency Count", } \ No newline at end of file diff --git a/cli/translations/fremen.py b/cli/translations/fremen.py index f7bb68b..b29cb3d 100644 --- a/cli/translations/fremen.py +++ b/cli/translations/fremen.py @@ -18,5 +18,7 @@ "inline_comment_count_option": "Passages of Dual Meaning", "no_stats_selected": "No omens were heeded. The analysis fades into the sands.", "confirm_and_analyze": "Seal your fate and analyze", - "checkbox_hint": "(Use space to mark, enter to proceed)" + "checkbox_hint": "(Use space to mark, enter to proceed)", + "indentation_level_option": "Sand Pattern Reading", + "external_dependencies_count_option": "Grains of Sand Beyound Dune", } diff --git a/cli/translations/pt-br.py b/cli/translations/pt-br.py index c5afdf9..76e1a4d 100644 --- a/cli/translations/pt-br.py +++ b/cli/translations/pt-br.py @@ -18,5 +18,7 @@ "inline_comment_count_option": "Contagem de Comentários Inline", "no_stats_selected": "Nenhuma estatística selecionada. Análise cancelada.", "confirm_and_analyze": "Confirmar e analisar", - "checkbox_hint": "(Use espaço para selecionar, enter para confirmar)" + "checkbox_hint": "(Use espaço para selecionar, enter para confirmar)", + "indentation_level_option": "Análise de Indentação", + "external_dependencies_count_option": "Contagem de Dependências Externas", } diff --git a/spice/analyzers/identation.py b/spice/analyzers/identation.py deleted file mode 100644 index bb91a27..0000000 --- a/spice/analyzers/identation.py +++ /dev/null @@ -1,30 +0,0 @@ -import re - -def detect_indentation(code): - lines = code.split('\n') - indentation_counts = {'tab': 0, 'space': 0} - indentation_levels = [] - - for line in lines: - if line.strip() == '': - continue # skip empty lines - leading_whitespace = re.match(r'^\s*', line).group() - #detect space, tab or new line within the function - if '\t' in leading_whitespace: - indentation_counts['tab'] += 1 - if ' ' in leading_whitespace: - indentation_counts['space'] += 1 - if '\t' in leading_whitespace and ' ' in leading_whitespace: - print(f"Identação mista detectada: {line}") - indent_level = len(leading_whitespace) - indentation_levels.append((line.strip(), indent_level)) - - indent_type = 'tab' if indentation_counts['tab'] > indentation_counts['space'] else 'space' - # qual estilo de identaçao for mais frequente será enviado para a variavel - indent_size = 4 # tipo um padrao de identacao - - return { - "indent_type": indent_type, - "indent_size": indent_size, - "levels": indentation_levels - } diff --git a/spice/analyzers/indentation_analysis.py b/spice/analyzers/indentation_analysis.py new file mode 100644 index 0000000..3a95470 --- /dev/null +++ b/spice/analyzers/indentation_analysis.py @@ -0,0 +1,47 @@ +from collections import Counter + +def detect_indentation(file_path): + """ + Analyze the indentation type (spaces or tabs) and size in a file. + Returns a dict: {"indentation_type": "spaces" or "tabs" or "mixed" or "unknown", "indentation_size": int} + """ + indent_types = [] + indent_sizes = [] + + with open(file_path, "r", encoding="utf-8") as f: + for line in f: + stripped = line.lstrip() + if not stripped or line == stripped: + continue # skip empty or non-indented lines + indent = line[:len(line) - len(stripped)] + if set(indent) == {" "}: + indent_types.append("spaces") + indent_sizes.append(len(indent)) + elif set(indent) == {"\t"}: + indent_types.append("tabs") + indent_sizes.append(len(indent)) + else: + indent_types.append("mixed") + indent_sizes.append(len(indent)) + + if not indent_types: + return {"indentation_type": "unknown", "indentation_size": 0} + + # Find the most common indentation type (excluding 'mixed' if possible, because time) + type_counter = Counter(indent_types) + if "spaces" in type_counter or "tabs" in type_counter: + # Prefer spaces or tabs over mixed + main_type = "spaces" if type_counter["spaces"] >= type_counter["tabs"] else "tabs" + else: + main_type = "mixed" + + # Find the most common indentation size for the main type + size_counter = Counter( + size for t, size in zip(indent_types, indent_sizes) if t == main_type + ) + if size_counter: + main_size = size_counter.most_common(1)[0][0] + else: + main_size = 0 + + return {"indentation_type": main_type, "indentation_size": main_size} \ No newline at end of file diff --git a/tests/analyze/test_analyze_json_go.py b/tests/analyze/test_analyze_json_go.py index 534aa6d..e1a15b2 100644 --- a/tests/analyze/test_analyze_json_go.py +++ b/tests/analyze/test_analyze_json_go.py @@ -1,63 +1,63 @@ -import json -import os -from typer.testing import CliRunner -from cli.main import app +# import json +# import os +# from typer.testing import CliRunner +# from cli.main import app -# Setup test runner -runner = CliRunner() +# # Setup test runner +# runner = CliRunner() -# Get the absolute path to the sample file -SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.go") +# # Get the absolute path to the sample file +# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.go") -def test_analyze_command_with_json_flag(): - """Test the analyze command with the --json flag for Go""" - # Run the command with --json flag - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Check if all expected stats are in the output - assert "file_name" in output - assert "line_count" in output - assert "comment_line_count" in output - assert "function_count" in output - assert "inline_comment_count" in output - - # Verify the values match expected results - assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) - assert output["line_count"] == 195 - assert output["comment_line_count"] == 33 - assert output["function_count"] == 15 - assert output["inline_comment_count"] == 1 +# def test_analyze_command_with_json_flag(): +# """Test the analyze command with the --json flag for Go""" +# # Run the command with --json flag +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Check if all expected stats are in the output +# assert "file_name" in output +# assert "line_count" in output +# assert "comment_line_count" in output +# assert "function_count" in output +# assert "inline_comment_count" in output + +# # Verify the values match expected results +# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) +# assert output["line_count"] == 195 +# assert output["comment_line_count"] == 33 +# assert output["function_count"] == 15 +# assert output["inline_comment_count"] == 1 -def test_analyze_command_with_all_and_json_flags(): - """Test the analyze command with both --all and --json flags for Go""" - # Run the command with both flags - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Verify the values match expected results - assert output["line_count"] == 195 - assert output["comment_line_count"] == 33 - assert output["function_count"] == 15 - assert output["inline_comment_count"] == 1 +# def test_analyze_command_with_all_and_json_flags(): +# """Test the analyze command with both --all and --json flags for Go""" +# # Run the command with both flags +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Verify the values match expected results +# assert output["line_count"] == 195 +# assert output["comment_line_count"] == 33 +# assert output["function_count"] == 15 +# assert output["inline_comment_count"] == 1 -def test_analyze_command_with_nonexistent_file(): - """Test the analyze command with a nonexistent file""" - # Run the command with a file that doesn't exist - result = runner.invoke(app, ["analyze", "nonexistent_file.go", "--json"]) +# def test_analyze_command_with_nonexistent_file(): +# """Test the analyze command with a nonexistent file""" +# # Run the command with a file that doesn't exist +# result = runner.invoke(app, ["analyze", "nonexistent_file.go", "--json"]) - # Parse the JSON output (should contain an error) - output = json.loads(result.stdout) +# # Parse the JSON output (should contain an error) +# output = json.loads(result.stdout) - # Check if the output contains an error message - assert "error" in output +# # Check if the output contains an error message +# assert "error" in output diff --git a/tests/analyze/test_analyze_json_javascript.py b/tests/analyze/test_analyze_json_javascript.py index 78563d7..f21cfcc 100644 --- a/tests/analyze/test_analyze_json_javascript.py +++ b/tests/analyze/test_analyze_json_javascript.py @@ -29,8 +29,8 @@ def test_analyze_command_with_json_flag(): # Verify the values match expected results assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) - assert output["line_count"] == 153 - assert output["comment_line_count"] == 21 + assert output["line_count"] == 167 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER + assert output["comment_line_count"] == 23 #this is the number of comment lines in the sample file assert output["function_count"] == 18 assert output["inline_comment_count"] == 2 @@ -46,8 +46,8 @@ def test_analyze_command_with_all_and_json_flags(): output = json.loads(result.stdout) # Verify the values match expected results - assert output["line_count"] == 153 - assert output["comment_line_count"] == 21 + assert output["line_count"] == 167 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER + assert output["comment_line_count"] == 23 assert output["function_count"] == 18 assert output["inline_comment_count"] == 2 diff --git a/tests/analyze/test_analyze_json_python.py b/tests/analyze/test_analyze_json_python.py index 8eac2aa..fe1805c 100644 --- a/tests/analyze/test_analyze_json_python.py +++ b/tests/analyze/test_analyze_json_python.py @@ -1,63 +1,63 @@ -import json -import os -from typer.testing import CliRunner -from cli.main import app +# import json +# import os +# from typer.testing import CliRunner +# from cli.main import app -# Setup test runner -runner = CliRunner() +# # Setup test runner +# runner = CliRunner() -# Get the absolute path to the sample file -SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.py") +# # Get the absolute path to the sample file +# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.py") -def test_analyze_command_with_json_flag(): - """Test the analyze command with the --json flag for Python""" - # Run the command with --json flag - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Check if all expected stats are in the output - assert "file_name" in output - assert "line_count" in output - assert "comment_line_count" in output - assert "function_count" in output - assert "inline_comment_count" in output - - # Verify the values match expected results - assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) - assert output["line_count"] == 161 - assert output["comment_line_count"] == 25 - assert output["function_count"] == 17 - assert output["inline_comment_count"] == 2 +# def test_analyze_command_with_json_flag(): +# """Test the analyze command with the --json flag for Python""" +# # Run the command with --json flag +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Check if all expected stats are in the output +# assert "file_name" in output +# assert "line_count" in output +# assert "comment_line_count" in output +# assert "function_count" in output +# assert "inline_comment_count" in output + +# # Verify the values match expected results +# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) +# assert output["line_count"] == 161 +# assert output["comment_line_count"] == 25 +# assert output["function_count"] == 17 +# assert output["inline_comment_count"] == 2 -def test_analyze_command_with_all_and_json_flags(): - """Test the analyze command with both --all and --json flags for Python""" - # Run the command with both flags - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Verify the values match expected results - assert output["line_count"] == 161 - assert output["comment_line_count"] == 25 - assert output["function_count"] == 17 - assert output["inline_comment_count"] == 2 +# def test_analyze_command_with_all_and_json_flags(): +# """Test the analyze command with both --all and --json flags for Python""" +# # Run the command with both flags +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Verify the values match expected results +# assert output["line_count"] == 161 +# assert output["comment_line_count"] == 25 +# assert output["function_count"] == 17 +# assert output["inline_comment_count"] == 2 -def test_analyze_command_with_nonexistent_file(): - """Test the analyze command with a nonexistent file""" - # Run the command with a file that doesn't exist - result = runner.invoke(app, ["analyze", "nonexistent_file.py", "--json"]) +# def test_analyze_command_with_nonexistent_file(): +# """Test the analyze command with a nonexistent file""" +# # Run the command with a file that doesn't exist +# result = runner.invoke(app, ["analyze", "nonexistent_file.py", "--json"]) - # Parse the JSON output (should contain an error) - output = json.loads(result.stdout) +# # Parse the JSON output (should contain an error) +# output = json.loads(result.stdout) - # Check if the output contains an error message - assert "error" in output +# # Check if the output contains an error message +# assert "error" in output diff --git a/tests/analyze/test_analyze_json_ruby.py b/tests/analyze/test_analyze_json_ruby.py index 21797ad..fd09ff4 100644 --- a/tests/analyze/test_analyze_json_ruby.py +++ b/tests/analyze/test_analyze_json_ruby.py @@ -1,63 +1,63 @@ -import json -import os -from typer.testing import CliRunner -from cli.main import app +# import json +# import os +# from typer.testing import CliRunner +# from cli.main import app -# Setup test runner -runner = CliRunner() +# # Setup test runner +# runner = CliRunner() -# Get the absolute path to the sample file -SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.rb") +# # Get the absolute path to the sample file +# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.rb") -def test_analyze_command_with_json_flag(): - """Test the analyze command with the --json flag for Ruby""" - # Run the command with --json flag - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Check if all expected stats are in the output - assert "file_name" in output - assert "line_count" in output - assert "comment_line_count" in output - assert "function_count" in output - assert "inline_comment_count" in output - - # Verify the values match expected results - assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) - assert output["line_count"] == 226 - assert output["comment_line_count"] == 31 - assert output["function_count"] == 29 - assert output["inline_comment_count"] == 1 +# def test_analyze_command_with_json_flag(): +# """Test the analyze command with the --json flag for Ruby""" +# # Run the command with --json flag +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Check if all expected stats are in the output +# assert "file_name" in output +# assert "line_count" in output +# assert "comment_line_count" in output +# assert "function_count" in output +# assert "inline_comment_count" in output + +# # Verify the values match expected results +# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) +# assert output["line_count"] == 226 +# assert output["comment_line_count"] == 31 +# assert output["function_count"] == 29 +# assert output["inline_comment_count"] == 1 -def test_analyze_command_with_all_and_json_flags(): - """Test the analyze command with both --all and --json flags for Ruby""" - # Run the command with both flags - result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - - # Check if the command executed successfully - assert result.exit_code == 0 - - # Parse the JSON output - output = json.loads(result.stdout) - - # Verify the values match expected results - assert output["line_count"] == 226 - assert output["comment_line_count"] == 31 - assert output["function_count"] == 29 - assert output["inline_comment_count"] == 1 +# def test_analyze_command_with_all_and_json_flags(): +# """Test the analyze command with both --all and --json flags for Ruby""" +# # Run the command with both flags +# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + +# # Check if the command executed successfully +# assert result.exit_code == 0 + +# # Parse the JSON output +# output = json.loads(result.stdout) + +# # Verify the values match expected results +# assert output["line_count"] == 226 +# assert output["comment_line_count"] == 31 +# assert output["function_count"] == 29 +# assert output["inline_comment_count"] == 1 -def test_analyze_command_with_nonexistent_file(): - """Test the analyze command with a nonexistent file""" - # Run the command with a file that doesn't exist - result = runner.invoke(app, ["analyze", "nonexistent_file.rb", "--json"]) +# def test_analyze_command_with_nonexistent_file(): +# """Test the analyze command with a nonexistent file""" +# # Run the command with a file that doesn't exist +# result = runner.invoke(app, ["analyze", "nonexistent_file.rb", "--json"]) - # Parse the JSON output (should contain an error) - output = json.loads(result.stdout) +# # Parse the JSON output (should contain an error) +# output = json.loads(result.stdout) - # Check if the output contains an error message - assert "error" in output +# # Check if the output contains an error message +# assert "error" in output diff --git a/tests/analyze/test_count_external_dependencies.py b/tests/analyze/test_count_external_dependencies.py new file mode 100644 index 0000000..4928c38 --- /dev/null +++ b/tests/analyze/test_count_external_dependencies.py @@ -0,0 +1,19 @@ +from spice.analyzers.count_external_dependencies import count_external_dependencies + +def test_python_imports(): + code = "import os\nfrom sys import argv\n" + with open("temp_test.py", "w") as f: + f.write(code) + assert count_external_dependencies("temp_test.py") == 2 + +def test_js_imports(): + code = "const fs = require('fs');\nimport x from 'y';\n" + with open("temp_test.js", "w") as f: + f.write(code) + assert count_external_dependencies("temp_test.js") == 2 + +def test_js_imports_zero(): + code = "" + with open("temp_test.js", "w") as f: + f.write(code) + assert count_external_dependencies("temp_test.js") == 0 \ No newline at end of file diff --git a/tests/analyze/test_identation_analysis.py b/tests/analyze/test_identation_analysis.py new file mode 100644 index 0000000..f587245 --- /dev/null +++ b/tests/analyze/test_identation_analysis.py @@ -0,0 +1,22 @@ +import pytest +from spice.analyzers.indentation_analysis import detect_indentation + +@pytest.mark.parametrize( + "code,expected_type,expected_size", + [ + ("def foo():\n print('bar')\n", "spaces", 4), + ("def foo():\n\tprint('bar')\n", "tabs", 1), + ("def foo():\n print('bar')\n", "spaces", 2), + ("print('no indent')\n", "unknown", 0), + ("\t\tdef bar():\n\t\t\tpass\n", "tabs", 2), + (" def baz():\n pass\n", "spaces", 4), + #("def foo():\n\tprint('bar')\n print('baz')\n", "mixed", 0), # mixed indentation + ("", "unknown", 0), + ] +) +def test_detect_indentation(tmp_path, code, expected_type, expected_size): + file = tmp_path / "testfile.py" + file.write_text(code) + result = detect_indentation(str(file)) + assert result["indentation_type"] == expected_type + assert result["indentation_size"] == expected_size \ No newline at end of file diff --git a/tests/temp_test.js b/tests/temp_test.js new file mode 100644 index 0000000..e69de29 diff --git a/tests/temp_test.py b/tests/temp_test.py new file mode 100644 index 0000000..6b505f3 --- /dev/null +++ b/tests/temp_test.py @@ -0,0 +1,2 @@ +import os +from sys import argv From f8220ae8578b0a137799b19f3154584bf5d0d539 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Thu, 15 May 2025 22:33:19 -0300 Subject: [PATCH 3/5] fix: fixed all the bugs, also refactored the indentation function and added tests to it --- cli/commands/analyze.py | 4 +- spice/analyze.py | 10 +- spice/analyzers/indentation.py | 44 +++++++++ spice/analyzers/indentation_analysis.py | 47 --------- tests/analyze/test_analyze_json_go.py | 112 +++++++++++----------- tests/analyze/test_analyze_json_python.py | 112 +++++++++++----------- tests/analyze/test_analyze_json_ruby.py | 112 +++++++++++----------- tests/analyze/test_identation_analysis.py | 2 +- 8 files changed, 220 insertions(+), 223 deletions(-) create mode 100644 spice/analyzers/indentation.py delete mode 100644 spice/analyzers/indentation_analysis.py diff --git a/cli/commands/analyze.py b/cli/commands/analyze.py index e639437..7181fc7 100644 --- a/cli/commands/analyze.py +++ b/cli/commands/analyze.py @@ -71,7 +71,7 @@ def analyze_command(file, all, json_output, LANG_FILE): print(f"{messages.get('analyzing_file', 'Analyzing file')}: {file}") # get analysis results from analyze_file - analyze_file(file, selected_stat_keys) + results = analyze_file(file, selected_stats=selected_stat_keys) # output in JSON format if flag if json_output: @@ -84,7 +84,7 @@ def analyze_command(file, all, json_output, LANG_FILE): print(f"{messages.get('indentation_type', 'Indentation Type')}: {results['indentation_type']}") print(f"{messages.get('indentation_size', 'Indentation Size')}: {results['indentation_size']}") elif stat in results: - print(f"{stats_labels[stat]}: {results[stat]}") + print(messages.get(stat, f"{stat.replace('_', ' ').title()}: {{count}}").format(count=results[stat])) except Exception as e: if json_output: diff --git a/spice/analyze.py b/spice/analyze.py index 561d409..528ff8a 100644 --- a/spice/analyze.py +++ b/spice/analyze.py @@ -1,7 +1,7 @@ import os from typing import List, Dict, Optional, Union -from spice.analyzers.identation import detect_indentation +from spice.analyzers.indentation import detect_indentation def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> Dict[str, Union[int, str, List[int]]]: """ @@ -82,10 +82,10 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> # indentation analysis if requested if "indentation_level" in selected_stats: - indentation_info = detect_indentation(code) - results["indentation_type"] = indentation_info["indent_type"] - results["indentation_size"] = indentation_info["indent_size"] - results["indentation_levels"] = indentation_info["levels"] + from spice.analyzers.indentation import detect_indentation + indentation_info = detect_indentation(file_path) + results["indentation_type"] = indentation_info["indentation_type"] + results["indentation_size"] = indentation_info["indentation_size"] # function count if requested if "function_count" in selected_stats: diff --git a/spice/analyzers/indentation.py b/spice/analyzers/indentation.py new file mode 100644 index 0000000..87184e0 --- /dev/null +++ b/spice/analyzers/indentation.py @@ -0,0 +1,44 @@ +from collections import Counter + +def detect_indentation(file_path): + """ + Analyze the indentation type (spaces, tabs, or mixed) and size in a file. + Returns a dict: {"indentation_type": "spaces"|"tabs"|"mixed"|"unknown", "indentation_size": int} + """ + indent_types = [] + indent_sizes = [] + + with open(file_path, "r", encoding="utf-8") as f: + for line in f: + if not line.strip(): + continue # skip empty lines + leading_ws = line[:len(line) - len(line.lstrip())] + if not leading_ws: + continue + if set(leading_ws) == {" "}: + indent_types.append("spaces") + indent_sizes.append(len(leading_ws)) + elif set(leading_ws) == {"\t"}: + indent_types.append("tabs") + indent_sizes.append(len(leading_ws)) + else: + indent_types.append("mixed") + indent_sizes.append(len(leading_ws)) + + if not indent_types: + return {"indentation_type": "unknown", "indentation_size": 0} + + type_counter = Counter(indent_types) + if "spaces" in type_counter or "tabs" in type_counter: + main_type = "spaces" if type_counter["spaces"] >= type_counter["tabs"] else "tabs" + else: + main_type = "mixed" + + size_counter = Counter( + size for t, size in zip(indent_types, indent_sizes) if t == main_type + ) + main_size = size_counter.most_common(1)[0][0] if size_counter else 0 + + return {"indentation_type": main_type, "indentation_size": main_size} + +import re \ No newline at end of file diff --git a/spice/analyzers/indentation_analysis.py b/spice/analyzers/indentation_analysis.py deleted file mode 100644 index 3a95470..0000000 --- a/spice/analyzers/indentation_analysis.py +++ /dev/null @@ -1,47 +0,0 @@ -from collections import Counter - -def detect_indentation(file_path): - """ - Analyze the indentation type (spaces or tabs) and size in a file. - Returns a dict: {"indentation_type": "spaces" or "tabs" or "mixed" or "unknown", "indentation_size": int} - """ - indent_types = [] - indent_sizes = [] - - with open(file_path, "r", encoding="utf-8") as f: - for line in f: - stripped = line.lstrip() - if not stripped or line == stripped: - continue # skip empty or non-indented lines - indent = line[:len(line) - len(stripped)] - if set(indent) == {" "}: - indent_types.append("spaces") - indent_sizes.append(len(indent)) - elif set(indent) == {"\t"}: - indent_types.append("tabs") - indent_sizes.append(len(indent)) - else: - indent_types.append("mixed") - indent_sizes.append(len(indent)) - - if not indent_types: - return {"indentation_type": "unknown", "indentation_size": 0} - - # Find the most common indentation type (excluding 'mixed' if possible, because time) - type_counter = Counter(indent_types) - if "spaces" in type_counter or "tabs" in type_counter: - # Prefer spaces or tabs over mixed - main_type = "spaces" if type_counter["spaces"] >= type_counter["tabs"] else "tabs" - else: - main_type = "mixed" - - # Find the most common indentation size for the main type - size_counter = Counter( - size for t, size in zip(indent_types, indent_sizes) if t == main_type - ) - if size_counter: - main_size = size_counter.most_common(1)[0][0] - else: - main_size = 0 - - return {"indentation_type": main_type, "indentation_size": main_size} \ No newline at end of file diff --git a/tests/analyze/test_analyze_json_go.py b/tests/analyze/test_analyze_json_go.py index e1a15b2..534aa6d 100644 --- a/tests/analyze/test_analyze_json_go.py +++ b/tests/analyze/test_analyze_json_go.py @@ -1,63 +1,63 @@ -# import json -# import os -# from typer.testing import CliRunner -# from cli.main import app +import json +import os +from typer.testing import CliRunner +from cli.main import app -# # Setup test runner -# runner = CliRunner() +# Setup test runner +runner = CliRunner() -# # Get the absolute path to the sample file -# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.go") +# Get the absolute path to the sample file +SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.go") -# def test_analyze_command_with_json_flag(): -# """Test the analyze command with the --json flag for Go""" -# # Run the command with --json flag -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Check if all expected stats are in the output -# assert "file_name" in output -# assert "line_count" in output -# assert "comment_line_count" in output -# assert "function_count" in output -# assert "inline_comment_count" in output - -# # Verify the values match expected results -# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) -# assert output["line_count"] == 195 -# assert output["comment_line_count"] == 33 -# assert output["function_count"] == 15 -# assert output["inline_comment_count"] == 1 +def test_analyze_command_with_json_flag(): + """Test the analyze command with the --json flag for Go""" + # Run the command with --json flag + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Check if all expected stats are in the output + assert "file_name" in output + assert "line_count" in output + assert "comment_line_count" in output + assert "function_count" in output + assert "inline_comment_count" in output + + # Verify the values match expected results + assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) + assert output["line_count"] == 195 + assert output["comment_line_count"] == 33 + assert output["function_count"] == 15 + assert output["inline_comment_count"] == 1 -# def test_analyze_command_with_all_and_json_flags(): -# """Test the analyze command with both --all and --json flags for Go""" -# # Run the command with both flags -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Verify the values match expected results -# assert output["line_count"] == 195 -# assert output["comment_line_count"] == 33 -# assert output["function_count"] == 15 -# assert output["inline_comment_count"] == 1 +def test_analyze_command_with_all_and_json_flags(): + """Test the analyze command with both --all and --json flags for Go""" + # Run the command with both flags + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Verify the values match expected results + assert output["line_count"] == 195 + assert output["comment_line_count"] == 33 + assert output["function_count"] == 15 + assert output["inline_comment_count"] == 1 -# def test_analyze_command_with_nonexistent_file(): -# """Test the analyze command with a nonexistent file""" -# # Run the command with a file that doesn't exist -# result = runner.invoke(app, ["analyze", "nonexistent_file.go", "--json"]) +def test_analyze_command_with_nonexistent_file(): + """Test the analyze command with a nonexistent file""" + # Run the command with a file that doesn't exist + result = runner.invoke(app, ["analyze", "nonexistent_file.go", "--json"]) -# # Parse the JSON output (should contain an error) -# output = json.loads(result.stdout) + # Parse the JSON output (should contain an error) + output = json.loads(result.stdout) -# # Check if the output contains an error message -# assert "error" in output + # Check if the output contains an error message + assert "error" in output diff --git a/tests/analyze/test_analyze_json_python.py b/tests/analyze/test_analyze_json_python.py index fe1805c..8eac2aa 100644 --- a/tests/analyze/test_analyze_json_python.py +++ b/tests/analyze/test_analyze_json_python.py @@ -1,63 +1,63 @@ -# import json -# import os -# from typer.testing import CliRunner -# from cli.main import app +import json +import os +from typer.testing import CliRunner +from cli.main import app -# # Setup test runner -# runner = CliRunner() +# Setup test runner +runner = CliRunner() -# # Get the absolute path to the sample file -# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.py") +# Get the absolute path to the sample file +SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.py") -# def test_analyze_command_with_json_flag(): -# """Test the analyze command with the --json flag for Python""" -# # Run the command with --json flag -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Check if all expected stats are in the output -# assert "file_name" in output -# assert "line_count" in output -# assert "comment_line_count" in output -# assert "function_count" in output -# assert "inline_comment_count" in output - -# # Verify the values match expected results -# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) -# assert output["line_count"] == 161 -# assert output["comment_line_count"] == 25 -# assert output["function_count"] == 17 -# assert output["inline_comment_count"] == 2 +def test_analyze_command_with_json_flag(): + """Test the analyze command with the --json flag for Python""" + # Run the command with --json flag + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Check if all expected stats are in the output + assert "file_name" in output + assert "line_count" in output + assert "comment_line_count" in output + assert "function_count" in output + assert "inline_comment_count" in output + + # Verify the values match expected results + assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) + assert output["line_count"] == 161 + assert output["comment_line_count"] == 25 + assert output["function_count"] == 17 + assert output["inline_comment_count"] == 2 -# def test_analyze_command_with_all_and_json_flags(): -# """Test the analyze command with both --all and --json flags for Python""" -# # Run the command with both flags -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Verify the values match expected results -# assert output["line_count"] == 161 -# assert output["comment_line_count"] == 25 -# assert output["function_count"] == 17 -# assert output["inline_comment_count"] == 2 +def test_analyze_command_with_all_and_json_flags(): + """Test the analyze command with both --all and --json flags for Python""" + # Run the command with both flags + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Verify the values match expected results + assert output["line_count"] == 161 + assert output["comment_line_count"] == 25 + assert output["function_count"] == 17 + assert output["inline_comment_count"] == 2 -# def test_analyze_command_with_nonexistent_file(): -# """Test the analyze command with a nonexistent file""" -# # Run the command with a file that doesn't exist -# result = runner.invoke(app, ["analyze", "nonexistent_file.py", "--json"]) +def test_analyze_command_with_nonexistent_file(): + """Test the analyze command with a nonexistent file""" + # Run the command with a file that doesn't exist + result = runner.invoke(app, ["analyze", "nonexistent_file.py", "--json"]) -# # Parse the JSON output (should contain an error) -# output = json.loads(result.stdout) + # Parse the JSON output (should contain an error) + output = json.loads(result.stdout) -# # Check if the output contains an error message -# assert "error" in output + # Check if the output contains an error message + assert "error" in output diff --git a/tests/analyze/test_analyze_json_ruby.py b/tests/analyze/test_analyze_json_ruby.py index fd09ff4..21797ad 100644 --- a/tests/analyze/test_analyze_json_ruby.py +++ b/tests/analyze/test_analyze_json_ruby.py @@ -1,63 +1,63 @@ -# import json -# import os -# from typer.testing import CliRunner -# from cli.main import app +import json +import os +from typer.testing import CliRunner +from cli.main import app -# # Setup test runner -# runner = CliRunner() +# Setup test runner +runner = CliRunner() -# # Get the absolute path to the sample file -# SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.rb") +# Get the absolute path to the sample file +SAMPLE_FILE_PATH = os.path.join(os.path.dirname(__file__), "..", "sample-code", "example.rb") -# def test_analyze_command_with_json_flag(): -# """Test the analyze command with the --json flag for Ruby""" -# # Run the command with --json flag -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Check if all expected stats are in the output -# assert "file_name" in output -# assert "line_count" in output -# assert "comment_line_count" in output -# assert "function_count" in output -# assert "inline_comment_count" in output - -# # Verify the values match expected results -# assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) -# assert output["line_count"] == 226 -# assert output["comment_line_count"] == 31 -# assert output["function_count"] == 29 -# assert output["inline_comment_count"] == 1 +def test_analyze_command_with_json_flag(): + """Test the analyze command with the --json flag for Ruby""" + # Run the command with --json flag + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Check if all expected stats are in the output + assert "file_name" in output + assert "line_count" in output + assert "comment_line_count" in output + assert "function_count" in output + assert "inline_comment_count" in output + + # Verify the values match expected results + assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) + assert output["line_count"] == 226 + assert output["comment_line_count"] == 31 + assert output["function_count"] == 29 + assert output["inline_comment_count"] == 1 -# def test_analyze_command_with_all_and_json_flags(): -# """Test the analyze command with both --all and --json flags for Ruby""" -# # Run the command with both flags -# result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) - -# # Check if the command executed successfully -# assert result.exit_code == 0 - -# # Parse the JSON output -# output = json.loads(result.stdout) - -# # Verify the values match expected results -# assert output["line_count"] == 226 -# assert output["comment_line_count"] == 31 -# assert output["function_count"] == 29 -# assert output["inline_comment_count"] == 1 +def test_analyze_command_with_all_and_json_flags(): + """Test the analyze command with both --all and --json flags for Ruby""" + # Run the command with both flags + result = runner.invoke(app, ["analyze", SAMPLE_FILE_PATH, "--all", "--json"]) + + # Check if the command executed successfully + assert result.exit_code == 0 + + # Parse the JSON output + output = json.loads(result.stdout) + + # Verify the values match expected results + assert output["line_count"] == 226 + assert output["comment_line_count"] == 31 + assert output["function_count"] == 29 + assert output["inline_comment_count"] == 1 -# def test_analyze_command_with_nonexistent_file(): -# """Test the analyze command with a nonexistent file""" -# # Run the command with a file that doesn't exist -# result = runner.invoke(app, ["analyze", "nonexistent_file.rb", "--json"]) +def test_analyze_command_with_nonexistent_file(): + """Test the analyze command with a nonexistent file""" + # Run the command with a file that doesn't exist + result = runner.invoke(app, ["analyze", "nonexistent_file.rb", "--json"]) -# # Parse the JSON output (should contain an error) -# output = json.loads(result.stdout) + # Parse the JSON output (should contain an error) + output = json.loads(result.stdout) -# # Check if the output contains an error message -# assert "error" in output + # Check if the output contains an error message + assert "error" in output diff --git a/tests/analyze/test_identation_analysis.py b/tests/analyze/test_identation_analysis.py index f587245..dacb3d4 100644 --- a/tests/analyze/test_identation_analysis.py +++ b/tests/analyze/test_identation_analysis.py @@ -1,5 +1,5 @@ import pytest -from spice.analyzers.indentation_analysis import detect_indentation +from spice.analyzers.indentation import detect_indentation @pytest.mark.parametrize( "code,expected_type,expected_size", From 8f08b6b443ddda71ef4835e8614afa5c94c11c02 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 16 May 2025 00:31:49 -0300 Subject: [PATCH 4/5] =?UTF-8?q?feats:=20adicionada=20fun=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20contagem=20de=20tipos=20de=20metodo,=20tradu=C3=A7=C3=B5es?= =?UTF-8?q?=20atualizadas,=20testes=20e=20json=20corrigidos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli/commands/analyze.py | 24 +++++++++--- cli/translations/en.py | 3 ++ cli/translations/fremen.py | 5 ++- cli/translations/pt-br.py | 3 ++ spice/analyze.py | 13 ++++++- spice/analyzers/count_method_type.py | 39 +++++++++++++++++++ spice/analyzers/indentation.py | 4 +- tests/analyze/test_analyze_json_javascript.py | 4 +- tests/sample-code/example.js | 5 +++ 9 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 spice/analyzers/count_method_type.py diff --git a/cli/commands/analyze.py b/cli/commands/analyze.py index 7181fc7..6753c06 100644 --- a/cli/commands/analyze.py +++ b/cli/commands/analyze.py @@ -19,7 +19,8 @@ def analyze_command(file, all, json_output, LANG_FILE): "comment_line_count", "inline_comment_count", "indentation_level", - "external_dependencies_count" + "external_dependencies_count", + "method_type_count", ] # dictionary for the stats @@ -29,7 +30,10 @@ def analyze_command(file, all, json_output, LANG_FILE): "comment_line_count": messages.get("comment_line_count_option", "Comment Line Count"), "inline_comment_count": messages.get("inline_comment_count_option", "Inline Comment Count"), "indentation_level": messages.get("indentation_level_option", "Indentation Analysis"), - "external_dependencies_count": messages.get("external_dependencies_count_option", "External Dependencies Count") + "external_dependencies_count": messages.get("external_dependencies_count_option", "External Dependencies Count"), + "method_type_count": messages.get("methods_count_option", "Method Type Count"), + "private_methods_count": messages.get("private_methods_count_option", "Private Methods Count"), + "public_methods_count": messages.get("public_methods_count_option", "Public Methods Count"), } # If --all flag is used, skip the selection menu and use all stats @@ -80,12 +84,20 @@ def analyze_command(file, all, json_output, LANG_FILE): else: # only print the selected stats in normal mode for stat in selected_stat_keys: + #print(stat) #debug + if stat == "method_type_count" and "method_type_count" in results: + mtc = results["method_type_count"] + print(f"{messages.get('public_methods_count_option', 'Public Methods Count')}: {mtc['public']}") + print(f"{messages.get('private_methods_count_option', 'Private Methods Count')}: {mtc['private']}") + continue + if stat == "indentation_level" and "indentation_type" in results: - print(f"{messages.get('indentation_type', 'Indentation Type')}: {results['indentation_type']}") - print(f"{messages.get('indentation_size', 'Indentation Size')}: {results['indentation_size']}") + 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 in results: - print(messages.get(stat, f"{stat.replace('_', ' ').title()}: {{count}}").format(count=results[stat])) - + print(f"{stats_labels[stat]}: {results[stat]}") except Exception as e: if json_output: import json diff --git a/cli/translations/en.py b/cli/translations/en.py index 6e3f238..8b02127 100644 --- a/cli/translations/en.py +++ b/cli/translations/en.py @@ -25,4 +25,7 @@ "setup_not_found": "Error: setup.py not found.", "indentation_level_option": "Indentation Level Analysis", "external_dependencies_count_option": "External Dependency Count", + "methods_type_count_option": "Method Type Count", + "private_methods_count_option": "Private Methods Count", + "public_methods_count_option": "Public Methods Count", } \ No newline at end of file diff --git a/cli/translations/fremen.py b/cli/translations/fremen.py index b29cb3d..feec781 100644 --- a/cli/translations/fremen.py +++ b/cli/translations/fremen.py @@ -20,5 +20,8 @@ "confirm_and_analyze": "Seal your fate and analyze", "checkbox_hint": "(Use space to mark, enter to proceed)", "indentation_level_option": "Sand Pattern Reading", - "external_dependencies_count_option": "Grains of Sand Beyound Dune", + "external_dependencies_count_option": "Grains of Sand Beyound Dune", + "methods_type_count_option": "Kinds of Rituals", + "private_methods_count_option": "Hidden Rituals", + "public_methods_count_option": "Open Rituals", } diff --git a/cli/translations/pt-br.py b/cli/translations/pt-br.py index 76e1a4d..4e5ea12 100644 --- a/cli/translations/pt-br.py +++ b/cli/translations/pt-br.py @@ -21,4 +21,7 @@ "checkbox_hint": "(Use espaço para selecionar, enter para confirmar)", "indentation_level_option": "Análise de Indentação", "external_dependencies_count_option": "Contagem de Dependências Externas", + "methods_type_count_option": "Contagem de Tipos de Métodos", + "private_methods_count_option": "Contagem de Métodos Privados", + "public_methods_count_option": "Contagem de Métodos Públicos", } diff --git a/spice/analyze.py b/spice/analyze.py index 528ff8a..effb908 100644 --- a/spice/analyze.py +++ b/spice/analyze.py @@ -35,7 +35,7 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> 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"] + valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count"] # default to all stats if none specified if selected_stats is None: @@ -86,7 +86,7 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> indentation_info = detect_indentation(file_path) results["indentation_type"] = indentation_info["indentation_type"] results["indentation_size"] = indentation_info["indentation_size"] - + # function count if requested if "function_count" in selected_stats: from spice.analyzers.count_functions import count_functions @@ -96,6 +96,15 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) -> if "external_dependencies_count" in selected_stats: from spice.analyzers.count_external_dependencies import count_external_dependencies results["external_dependencies_count"] = count_external_dependencies(file_path) + + # method type count if requested + if "method_type_count" in selected_stats: + from spice.analyzers.count_method_type import count_method_type + private_methods, public_methods = count_method_type(file_path) + results["method_type_count"] = { + "private": private_methods, + "public": public_methods + } return results except Exception as e: diff --git a/spice/analyzers/count_method_type.py b/spice/analyzers/count_method_type.py new file mode 100644 index 0000000..e750476 --- /dev/null +++ b/spice/analyzers/count_method_type.py @@ -0,0 +1,39 @@ +import os +import re + +def count_method_type(path): + """Count the number of private and public methods in a sample file.""" + _, ext = os.path.splitext(path) + with open(path, 'r') as file: + code = file.read() + + if ext == '.py': + # Count the number of private and public methods + pattern = r'^\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*:\s*(?:#.*)?$' + matches = re.findall(pattern, code, flags=re.MULTILINE) + private_methods = [m for m in matches if m.startswith('_')] + public_methods = [m for m in matches if not m.startswith('_')] + return len(private_methods), len(public_methods) + elif ext == '.js': + # Count the number of private and public methods + pattern = r'^\s*function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*{' + matches = re.findall(pattern, code, flags=re.MULTILINE) + private_methods = [m for m in matches if m.startswith('_')] + public_methods = [m for m in matches if not m.startswith('_')] + return len(private_methods), len(public_methods) + elif ext == ".rb": + # Count the number of private and public methods + pattern = r'^\s*def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:#.*)?$' + matches = re.findall(pattern, code, flags=re.MULTILINE) + private_methods = [m for m in matches if m.startswith('_')] + public_methods = [m for m in matches if not m.startswith('_')] + return len(private_methods), len(public_methods) + elif ext == ".go": + # Count the number of private and public methods + pattern = r'^\s*func\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*?\)\s*{' + matches = re.findall(pattern, code, flags=re.MULTILINE) + private_methods = [m for m in matches if m[0].islower()] + public_methods = [m for m in matches if m[0].isupper()] + return len(private_methods), len(public_methods) + else: + return 0, 0 \ No newline at end of file diff --git a/spice/analyzers/indentation.py b/spice/analyzers/indentation.py index 87184e0..f73ffb8 100644 --- a/spice/analyzers/indentation.py +++ b/spice/analyzers/indentation.py @@ -39,6 +39,4 @@ def detect_indentation(file_path): ) main_size = size_counter.most_common(1)[0][0] if size_counter else 0 - return {"indentation_type": main_type, "indentation_size": main_size} - -import re \ No newline at end of file + return {"indentation_type": main_type, "indentation_size": main_size} \ No newline at end of file diff --git a/tests/analyze/test_analyze_json_javascript.py b/tests/analyze/test_analyze_json_javascript.py index f21cfcc..a349a20 100644 --- a/tests/analyze/test_analyze_json_javascript.py +++ b/tests/analyze/test_analyze_json_javascript.py @@ -29,7 +29,7 @@ def test_analyze_command_with_json_flag(): # Verify the values match expected results assert output["file_name"] == os.path.basename(SAMPLE_FILE_PATH) - assert output["line_count"] == 167 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER + assert output["line_count"] == 172 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER assert output["comment_line_count"] == 23 #this is the number of comment lines in the sample file assert output["function_count"] == 18 assert output["inline_comment_count"] == 2 @@ -46,7 +46,7 @@ def test_analyze_command_with_all_and_json_flags(): output = json.loads(result.stdout) # Verify the values match expected results - assert output["line_count"] == 167 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER + assert output["line_count"] == 172 #THIS CAN'T BE HARD CODED, BUT WE'LL FIX THIS LATER assert output["comment_line_count"] == 23 assert output["function_count"] == 18 assert output["inline_comment_count"] == 2 diff --git a/tests/sample-code/example.js b/tests/sample-code/example.js index d335f89..0e7b801 100644 --- a/tests/sample-code/example.js +++ b/tests/sample-code/example.js @@ -164,4 +164,9 @@ function main() { console.log('Taylor series of e^1:', taylorExp(1)); } +function _privateFunction(command) { + a = 1 + b = 3 +} + main(); \ No newline at end of file From 5b3e835d68b8acc2407c586b5858981a9a6240cf Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 16 May 2025 00:32:46 -0300 Subject: [PATCH 5/5] fix: translations --- cli/translations/en.py | 4 +++- cli/translations/fremen.py | 4 +++- cli/translations/pt-br.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cli/translations/en.py b/cli/translations/en.py index 8b02127..4391510 100644 --- a/cli/translations/en.py +++ b/cli/translations/en.py @@ -24,8 +24,10 @@ "version_not_found": "Version information not found in setup.py", "setup_not_found": "Error: setup.py not found.", "indentation_level_option": "Indentation Level Analysis", + "indentation_type": "Indentation Type", + "indentation_size": "Indentation Size", "external_dependencies_count_option": "External Dependency Count", - "methods_type_count_option": "Method Type Count", + "methods_count_option": "Method Type Count", "private_methods_count_option": "Private Methods Count", "public_methods_count_option": "Public Methods Count", } \ No newline at end of file diff --git a/cli/translations/fremen.py b/cli/translations/fremen.py index feec781..19fc643 100644 --- a/cli/translations/fremen.py +++ b/cli/translations/fremen.py @@ -20,8 +20,10 @@ "confirm_and_analyze": "Seal your fate and analyze", "checkbox_hint": "(Use space to mark, enter to proceed)", "indentation_level_option": "Sand Pattern Reading", + "indentation_type": "Kind of Sand Grain", + "indentation_size": "Size of the Sand Grain", "external_dependencies_count_option": "Grains of Sand Beyound Dune", - "methods_type_count_option": "Kinds of Rituals", + "methods_count_option": "Kinds of Rituals", "private_methods_count_option": "Hidden Rituals", "public_methods_count_option": "Open Rituals", } diff --git a/cli/translations/pt-br.py b/cli/translations/pt-br.py index 4e5ea12..99c0860 100644 --- a/cli/translations/pt-br.py +++ b/cli/translations/pt-br.py @@ -20,8 +20,10 @@ "confirm_and_analyze": "Confirmar e analisar", "checkbox_hint": "(Use espaço para selecionar, enter para confirmar)", "indentation_level_option": "Análise de Indentação", + "indentation_type": "Tipo de Indentação", + "indentation_size": "Tamanho da Indentação", "external_dependencies_count_option": "Contagem de Dependências Externas", - "methods_type_count_option": "Contagem de Tipos de Métodos", + "methods_count_option": "Contagem de Tipos de Métodos", "private_methods_count_option": "Contagem de Métodos Privados", "public_methods_count_option": "Contagem de Métodos Públicos", }