mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 20:17:42 +00:00
Fix: language composition percentages diluted by unrecognised files
determine_programming_language_composition computed percentages over the total file count, so files belonging to no supported language (images, plain text, licenses, lock files) diluted the numbers — a repository with 2 Python files and 23 assets reported Python at 8%. Percentages are now relative to files matching at least one supported language, and the counting runs in a single pass over the file list instead of one pass per language. The only consumer (ProjectConfig.autogenerate) sorts languages by percentage, which is unaffected by the uniform rescaling.
This commit is contained in:
1 parent
3c241bab0d
commit
a7501b1c69
3 files changed
+75
-23
No files matched your search
+4
-1
@@ -26,7 +26,10 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
(which does not correctly unpack structured output) #1042
|
||||
- Fix: Project-specific filtering of files for source files ignored the language backend.
|
||||
The check is really only possible for LSP.
|
||||
- Fix: File system permission errors during gitignore scanning were not caught #1624
|
||||
- Fix: File system permission errors during gitignore scanning were not caught #1624
|
||||
- During project creation, language composition percentages are now computed relative to the total number
|
||||
of recognised source files instead of all files, i.e. unrecognised files are ignored in the percentage
|
||||
computation.
|
||||
|
||||
* CLI:
|
||||
- Fix `--project-from-cwd` hijacking git worktrees nested under a Serena project. `find_project_root`
|
||||
|
||||
@@ -31,36 +31,40 @@ def determine_programming_language_composition(repo_path: str) -> dict[Language,
|
||||
"""
|
||||
Determine the programming language composition of a repository.
|
||||
|
||||
:param repo_path: Path to the repository to analyze
|
||||
Percentages are computed relative to the number of files that match at least
|
||||
one supported language, not the total file count. This prevents files that
|
||||
belong to no supported language (images, plain text, licenses, lock files, etc.)
|
||||
from diluting language percentages in repositories where such files dominate.
|
||||
|
||||
:return: Dictionary mapping languages to percentages of files matching each language
|
||||
:param repo_path: Path to the repository to analyze
|
||||
:return: Dictionary mapping languages to percentages of recognised source files
|
||||
matching each language (denominator = files matched by at least one language)
|
||||
"""
|
||||
all_files = find_all_non_ignored_files(repo_path)
|
||||
|
||||
if not all_files:
|
||||
return {}
|
||||
|
||||
# Count files for each language
|
||||
# collect all language matchers once
|
||||
all_languages = list(Language.iter_all(include_experimental=False))
|
||||
matchers = {lang: lang.get_source_fn_matcher() for lang in all_languages}
|
||||
|
||||
# count files per language in a single pass over the files
|
||||
language_counts: dict[Language, int] = {}
|
||||
total_files = len(all_files)
|
||||
|
||||
for language in Language.iter_all(include_experimental=False):
|
||||
matcher = language.get_source_fn_matcher()
|
||||
count = 0
|
||||
|
||||
for file_path in all_files:
|
||||
# Use just the filename for matching, not the full path
|
||||
filename = os.path.basename(file_path)
|
||||
recognised_files = 0
|
||||
for file_path in all_files:
|
||||
# Use just the filename for matching, not the full path
|
||||
filename = os.path.basename(file_path)
|
||||
matched_any = False
|
||||
for lang, matcher in matchers.items():
|
||||
if matcher.is_relevant_filename(filename):
|
||||
count += 1
|
||||
language_counts[lang] = language_counts.get(lang, 0) + 1
|
||||
matched_any = True
|
||||
if matched_any:
|
||||
recognised_files += 1
|
||||
|
||||
if count > 0:
|
||||
language_counts[language] = count
|
||||
if recognised_files == 0:
|
||||
return {}
|
||||
|
||||
# Convert counts to percentages
|
||||
language_percentages: dict[Language, float] = {}
|
||||
for language, count in language_counts.items():
|
||||
percentage = (count / total_files) * 100
|
||||
language_percentages[language] = round(percentage, 2)
|
||||
|
||||
return language_percentages
|
||||
# convert to percentages relative to recognised source files only
|
||||
return {lang: round(count / recognised_files * 100, 2) for lang, count in language_counts.items()}
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Unit tests for :func:`serena.util.inspection.determine_programming_language_composition`."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from serena.util.inspection import determine_programming_language_composition
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
|
||||
def _touch(directory: Path, *names: str) -> None:
|
||||
for name in names:
|
||||
(directory / name).write_text("content", encoding="utf-8")
|
||||
|
||||
|
||||
class TestDetermineProgrammingLanguageComposition:
|
||||
def test_single_language_repo(self, tmp_path: Path) -> None:
|
||||
_touch(tmp_path, "a.py", "b.py", "c.py")
|
||||
composition = determine_programming_language_composition(str(tmp_path))
|
||||
assert composition[Language.PYTHON] == 100.0
|
||||
|
||||
def test_unrecognised_files_do_not_dilute_percentages(self, tmp_path: Path) -> None:
|
||||
# 2 source files vs many files that belong to no supported language
|
||||
_touch(tmp_path, "main.py", "util.py")
|
||||
_touch(tmp_path, *[f"note_{i}.txt" for i in range(20)])
|
||||
_touch(tmp_path, "logo.png", "LICENSE", "data.csv")
|
||||
|
||||
composition = determine_programming_language_composition(str(tmp_path))
|
||||
|
||||
# previously: 2 / 25 files = 8% — now the denominator is recognised source files only
|
||||
assert composition[Language.PYTHON] == 100.0
|
||||
|
||||
def test_mixed_language_percentages_relative_to_recognised_files(self, tmp_path: Path) -> None:
|
||||
_touch(tmp_path, "a.py", "b.py", "c.py", "d.go")
|
||||
_touch(tmp_path, *[f"asset_{i}.dat" for i in range(50)])
|
||||
|
||||
composition = determine_programming_language_composition(str(tmp_path))
|
||||
|
||||
assert composition[Language.PYTHON] == 75.0
|
||||
assert composition[Language.GO] == 25.0
|
||||
|
||||
def test_repo_without_recognised_files(self, tmp_path: Path) -> None:
|
||||
_touch(tmp_path, "readme.txt", "logo.png")
|
||||
assert determine_programming_language_composition(str(tmp_path)) == {}
|
||||
|
||||
def test_empty_repo(self, tmp_path: Path) -> None:
|
||||
assert determine_programming_language_composition(str(tmp_path)) == {}
|
||||
Reference in new issue
Block a user