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
63 changes: 46 additions & 17 deletions gitgalaxy/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,37 @@ def _resolve_class_start_match(match: re.Match, groups_count: int) -> tuple[Opti
{"async", "static", "public", "private", "protected", "abstract", "readonly", "override", "get", "set"}
)

# #2547: satellite names the structural slicer synthesizes for languages/modes with
# no real same-file call graph -- Mode D's (_slice_by_keywords) top-level loose-code
# bucket ("__global_context__", see ~5063) and Mode E's (_slice_by_terminator)
# per-statement-type bucket ("Declarative_Block", see ~5176/5265, or a
# "<KEYWORD>_Statement" name derived from the igniter match, see ~5235). None of
# these are ever real, callable identifiers, so they must never be eligible for
# orphan/duplicate classification below -- their name can never legitimately appear
# a second time in the file, and treating that as "orphaned" or "duplicated" just
# measures the slicer's own bucketing instead of real dead/copy-pasted code. Both
# `Main` and `Anonymous_Block` (Mode D's own top-of-scope/fallback names) plus
# `Unknown_Sat` (legacy) round out the same family. The slicer also appends
# `_[Truncated]`/`_[Unterminated]` to several of these when a scope runs off the end
# of a block (~5039, ~5043, ~5277), so those suffixes are stripped before matching.
_SYNTHETIC_SATELLITE_NAMES = frozenset(
{"Unknown_Sat", "Anonymous_Block", "Main", "Declarative_Block", "__global_context__"}
)
_SYNTHETIC_SATELLITE_SUFFIXES = ("_[Truncated]", "_[Unterminated]")


def _is_synthetic_satellite_name(name: str) -> bool:
base = name
for suffix in _SYNTHETIC_SATELLITE_SUFFIXES:
if base.endswith(suffix):
base = base[: -len(suffix)]
break
if base in _SYNTHETIC_SATELLITE_NAMES:
return True
# Mode E never captures a real identifier for SQL's igniter-based naming --
# it always synthesizes "<IGNITER-KEYWORD>_Statement" (~5235).
return bool(re.fullmatch(r"[A-Z0-9]+_Statement", base))


class StructuralExtractor:
"""
Expand Down Expand Up @@ -1300,23 +1331,21 @@ def splice(
func_name = func.get("name", "")
usage_status = 0 # 0 = Normal

# Check for Duplicates: same name AND materially the same body,
# defined multiple times in the same file.
if (
func_name
and func_name_counts[func_name] > 1
and body_hash_counts[(func_name, func_body_hashes[id(func)])] > 1
):
usage_status = 2 # 2 = Duplicate
duplicate_count += 1
elif len(func_name) > 3 and func_name not in {
"Unknown_Sat",
"Anonymous_Block",
"Main",
"Declarative_Block",
}:
# If the function name only exists where it was defined, it's an orphan
if token_counts[func_name] <= 1:
# #2547: synthetic slicer bucket names (Mode D's "__global_context__",
# Mode E's "<KEYWORD>_Statement"/"Declarative_Block", etc.) are never
# real callable identifiers -- skip them for BOTH the duplicate and
# orphan checks below, not just the orphan one.
if func_name and not _is_synthetic_satellite_name(func_name):
# Check for Duplicates: same name AND materially the same body,
# defined multiple times in the same file.
if (
func_name_counts[func_name] > 1
and body_hash_counts[(func_name, func_body_hashes[id(func)])] > 1
):
usage_status = 2 # 2 = Duplicate
duplicate_count += 1
elif len(func_name) > 3 and token_counts[func_name] <= 1:
# If the function name only exists where it was defined, it's an orphan
orphan_count += 1
usage_status = 1 # 1 = Orphan / Unused

Expand Down
70 changes: 70 additions & 0 deletions tests/core_engine/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,76 @@ def test_detector_duplicate_logic_is_scope_blind_to_shadowed_same_name_helpers()
)


def test_detector_orphan_census_excludes_synthetic_slicer_names():
"""
Regression test for #2547: languages sliced by Mode D (_slice_by_keywords) or
Mode E (_slice_by_terminator) synthesize bucket names for structural chunks that
were never real, callable functions -- Mode D's "__global_context__" for
top-level loose code sitting before the first real scope, and Mode E's
"<KEYWORD>_Statement"/"Declarative_Block" per-statement buckets for SQL. These
must never be eligible for orphan/duplicate classification: a synthetic name can
never legitimately appear a second time in the file, so without this exclusion
they were ALWAYS flagged "orphaned", inflating orphaned_logic with non-function
shapes instead of real dead code.
"""
# Mode D: shell. `. ./b.sh` is real top-level code (not a comment) preceding the
# first function -- gets bucketed into a synthetic "__global_context__" satellite.
shell_detector = StructuralExtractor("shell", MOCK_LANG_DEFS)
shell_code = (
". ./b.sh\n"
"\n"
"active_helper() {\n"
" echo hi\n"
"}\n"
"\n"
"forgotten_orphan() {\n"
" echo bye\n"
"}\n"
"\n"
"main_process() {\n"
" active_helper\n"
"}\n"
)
shell_result = shell_detector.splice(shell_code, "")
shell_names = [f["name"] for f in shell_result["functions"]]
assert "__global_context__" in shell_names, "Test setup didn't reproduce the synthetic bucket -- fixture drifted"

synthetic_flagged = [
f["name"]
for f in shell_result["functions"]
if f["name"] == "__global_context__" and f.get("usage_status") != 0
]
assert synthetic_flagged == [], "__global_context__ (non-function slicer bucket) was flagged as orphan/duplicate!"

real_orphans = [f["name"] for f in shell_result["functions"] if f.get("usage_status") == 1]
assert set(real_orphans) == {"forgotten_orphan", "main_process"}, f"Real orphan detection regressed: {real_orphans}"
assert shell_result["equations"].get("orphaned_logic", 0) == 2, (
"orphaned_logic should count only the 2 real uncalled functions, not the synthetic bucket!"
)

# Mode E: sql. Every top-level statement becomes its own satellite, named
# generically from its leading keyword ("SELECT_Statement", "CREATE_Statement",
# ...) -- never a real captured identifier, so none should be orphan-eligible.
sql_detector = StructuralExtractor("sql", MOCK_LANG_DEFS)
sql_code = (
"SELECT * FROM users;\n"
"INSERT INTO users (id) VALUES (1);\n"
"CREATE INDEX idx_users_id ON users (id);\n"
)
sql_result = sql_detector.splice(sql_code, "")
sql_names = [f["name"] for f in sql_result["functions"]]
assert any(name.endswith("_Statement") for name in sql_names), (
"Test setup didn't reproduce Mode E's synthetic per-statement bucket -- fixture drifted"
)

assert all(f.get("usage_status") == 0 for f in sql_result["functions"]), (
f"A synthetic Mode E statement bucket was flagged as orphan/duplicate: {sql_result['functions']}"
)
assert sql_result["equations"].get("orphaned_logic", 0) == 0, (
"orphaned_logic should be 0 -- SQL statements have no real callable names to be orphaned!"
)


def test_detector_c_macro_dead_branch_shield():
"""
Proves the Mode B Preprocessor Shield successfully blanks out dead
Expand Down
Loading
Loading