Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cli/commands/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def analyze_command(file, all, json_output, LANG_FILE):
"indentation_level",
"external_dependencies_count",
"method_type_count",
"comment_ratio",
]

# dictionary for the stats
Expand All @@ -34,8 +35,9 @@ def analyze_command(file, all, json_output, LANG_FILE):
"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"),
"comment_ratio": messages.get("comment_ratio_option", "Comment to Code Ratio"),
}

# If --all flag is used, skip the selection menu and use all stats
if all:
selected_stat_keys = available_stats
Expand Down
1 change: 1 addition & 0 deletions cli/translations/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@
"methods_count_option": "Method Type Count",
"private_methods_count_option": "Private Methods Count",
"public_methods_count_option": "Public Methods Count",
"comment_ratio_option": "Comment to Code Ratio",
}
1 change: 1 addition & 0 deletions cli/translations/fremen.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
"methods_count_option": "Kinds of Rituals",
"private_methods_count_option": "Hidden Rituals",
"public_methods_count_option": "Open Rituals",
"comment_ratio_option": "Whisper to Sand Ratio",
}
1 change: 1 addition & 0 deletions cli/translations/pt-br.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
"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",
"comment_ratio_option": "Relação Comentário/Código",
}
7 changes: 6 additions & 1 deletion spice/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "method_type_count"]
valid_stats = ["line_count", "function_count", "comment_line_count", "inline_comment_count", "indentation_level", "external_dependencies_count", "method_type_count", "comment_ratio"]

# default to all stats if none specified
if selected_stats is None:
Expand Down Expand Up @@ -105,6 +105,11 @@ def analyze_file(file_path: str, selected_stats: Optional[List[str]] = None) ->
"private": private_methods,
"public": public_methods
}

# comment to code ratio if requested
if "comment_ratio" in selected_stats:
from spice.analyzers.count_comment_ratio import count_comment_ratio
results["comment_ratio"] = count_comment_ratio(file_path)
return results

except Exception as e:
Expand Down
70 changes: 70 additions & 0 deletions spice/analyzers/count_comment_ratio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
import re

def count_comment_ratio(path):
total_comments = 0
total_lines = 0

file_types = {
'.py': {'single': [r'#'], 'multi': []},
'.js': {'single': [r'//'], 'multi': [('/*', '*/')]},
'.go': {'single': [r'//'], 'multi': [('/*', '*/')]},
'.rb': {'single': [r'#'], 'multi': []},
}

def analyze_file(file_path, ext):
nonlocal total_comments, total_lines
single_patterns = [re.compile(pat) for pat in file_types[ext]['single']]
multi_delims = file_types[ext]['multi']
in_multiline = False

try:
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
stripped = line.strip()
if not stripped:
continue # ignora linha em branco
total_lines += 1

# Dentro de comentário multilinha
if in_multiline:
total_comments += 1
for _, end in multi_delims:
if end in stripped:
in_multiline = False
continue

# Início de comentário multilinha
found_multiline = False
for start, end in multi_delims:
if start in stripped:
total_comments += 1
found_multiline = True
if end not in stripped:
in_multiline = True
break
if found_multiline:
continue

# Comentário de linha única (ou inline)
if any(pat.search(line) for pat in single_patterns):
total_comments += 1
except Exception as e:
print(f"Erro ao ler arquivo: {file_path}, erro: {e}")

if os.path.isfile(path):
ext = os.path.splitext(path)[1]
if ext in file_types:
analyze_file(path, ext)
else:
for root, _, files in os.walk(path):
for filename in files:
ext = os.path.splitext(filename)[1]
if ext in file_types:
analyze_file(os.path.join(root, filename), ext)

if total_lines == 0:
return "0.00%"

percentage = (total_comments / total_lines) * 100
return f"{percentage:.2f}%"
Empty file added temp_test.js
Empty file.
2 changes: 2 additions & 0 deletions temp_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import os
from sys import argv
58 changes: 58 additions & 0 deletions tests/analyze/test_analyze_comment_ratio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from spice.analyzers.count_comment_ratio import count_comment_ratio
import os

def test_count_comment_ratio():
# python
py_code = """
# This is a comment
def foo():
pass # Inline comment
"""
with open("temp_test.py", "w") as f:
f.write(py_code)
assert count_comment_ratio("temp_test.py") == "66.67%"
os.remove("temp_test.py")

# javascript
js_code = """
// This is a comment
function foo() {
return 42; // Inline comment
}
/*
Multi-line
comment
*/
"""
with open("temp_test.js", "w") as f:
f.write(js_code)
assert count_comment_ratio("temp_test.js") == "75.00%"
os.remove("temp_test.js")

# go
go_code = """
// This is a comment
func foo() int {
return 42 // Inline comment
}
/*
Multi-line
comment
*/
"""
with open("temp_test.go", "w") as f:
f.write(go_code)
assert count_comment_ratio("temp_test.go") == "75.00%"
os.remove("temp_test.go")

# ruby
rb_code = """
# This is a comment
def foo
42 # Inline comment
end
"""
with open("temp_test.rb", "w") as f:
f.write(rb_code)
assert count_comment_ratio("temp_test.rb") == "50.00%"
os.remove("temp_test.rb")
62 changes: 62 additions & 0 deletions tests/analyze/test_analyze_method_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from spice.analyzers.count_method_type import count_method_type
import os

def test_count_method_type():
# Python
py_code = """
class MyClass:
def public_method(self):
pass

def _private_method(self):
pass
"""
with open("temp_test.py", "w") as f:
f.write(py_code)
assert count_method_type("temp_test.py") == (1, 1) # (private, public)
os.remove("temp_test.py")

# JavaScript
js_code = """
class MyClass {
publicMethod() {
// public
}
_privateMethod() {
// private by convention
}
}
"""
with open("temp_test.js", "w") as f:
f.write(js_code)
# Your function only matches "function name() {" syntax, so this will return (0, 0)
# To match class methods, update your function or adjust the test:
assert count_method_type("temp_test.js") == (0, 0)
os.remove("temp_test.js")

# Go
go_code = """
type MyStruct struct{}

func (m MyStruct) PublicMethod() {}
func (m MyStruct) privateMethod() {}
"""
with open("temp_test.go", "w") as f:
f.write(go_code)
assert count_method_type("temp_test.go") == (0, 0) # (private, public)
os.remove("temp_test.go")

# Ruby
rb_code = """
class MyClass
def public_method
end

def _private_method
end
end
"""
with open("temp_test.rb", "w") as f:
f.write(rb_code)
assert count_method_type("temp_test.rb") == (1, 1) # (private, public)
os.remove("temp_test.rb")