Skip to content

fix: preserve Kotlin annotations + resolve C# namespace imports (#295, #310)#353

Open
azizur100389 wants to merge 1 commit intotirth8205:mainfrom
azizur100389:fix/csharp-imports-and-kotlin-annotations
Open

fix: preserve Kotlin annotations + resolve C# namespace imports (#295, #310)#353
azizur100389 wants to merge 1 commit intotirth8205:mainfrom
azizur100389:fix/csharp-imports-and-kotlin-annotations

Conversation

@azizur100389
Copy link
Copy Markdown
Contributor

Summary

Two parser correctness fixes that restore metadata and resolution paths lost by v2.3.2.

Closes #295, closes #310.


Fix 1 — Kotlin (and Java/C#/Python) annotation preservation (#295)

_extract_functions already walked tree-sitter modifiers > annotation children to collect decorators, but used the list only for test-kind detection and then discarded it. Kotlin @Composable, @HiltViewModel, @Inject; Java @Entity, @Autowired; C# [HttpGet]; Python decorators — all were missing from the persisted node.

_extract_classes did not extract class-level annotations at all, so @HiltViewModel class MyViewModel, @AndroidEntryPoint, etc. lost their annotation metadata entirely.

Fix:

  • New module-level _extract_annotation_list(node) helper that centralises the Java/Kotlin/C# modifiers walk and the Python decorated_definition walk.
  • _extract_functions now persists annotations to node.modifiers (comma-separated string) AND node.extra["decorators"] (list) on every Function / Test node.
  • _extract_classes calls the same helper to preserve class-level annotations.

Consumers can now write queries like "show me all @Composable functions" or "find @HiltViewModel classes" — both currently impossible on Kotlin codebases.

Fix 2 — C# namespace-based importers_of resolution (#310)

C# using ACME.Core; directives produce an IMPORTS_FROM edge whose target is the raw namespace string ("ACME.Core"), not a file path. importers_of looked edges up by file path and never matched a namespace, so it returned [] for every .cs file — breaking get_impact_radius, detect_changes, and dead-code analysis on C# codebases.

Fix:

  • New _extract_csharp_namespaces(root_node) helper handles both tree-sitter shapes:
    • block form: namespace_declaration
    • C# 10+ file-scoped: file_scoped_namespace_declaration
  • parse_bytes tags each C# File node with the namespaces it declares in extra["csharp_namespaces"].
  • tools/query.py::query_graph adds a namespace fallback to importers_of: when the target is a .cs file, look up its declared namespaces and also search edges_by_target for each namespace string. Deduped against the primary lookup so duplicates never appear.

This mirrors the existing bare-name fallback already used by callers_of and inheritors_of (see #87). No parser restructuring needed — just an additional lookup path in the query layer.


Tests added (10 new tests in tests/test_multilang.py)

TestKotlinAnnotations — 5 tests:

Test Purpose
test_hilt_viewmodel_annotation_on_class @HiltViewModel persisted on Class node
test_composable_annotation_on_function @Composable persisted on Function node
test_multiple_annotations_on_function multiple @Inject + @field:JvmField both preserved
test_unannotated_function_has_none_modifiers regression guard: no leak of empty string / empty list
test_test_annotation_still_triggers_test_kind guard: pre-existing @Test → Test kind promotion still works

TestCSharpNamespaceResolution — 5 tests:

Test Purpose
test_file_scoped_namespace_tagged_on_file_node C# 10+ namespace Foo; form
test_block_namespace_tagged_on_file_node classic namespace Foo { ... } form
test_multiple_namespaces_in_one_file both namespaces kept
test_non_csharp_file_has_no_namespace_tag scope guard
test_importers_of_resolves_namespace_to_file end-to-end: write Core.cs + App.cs + Unrelated.cs, query importers_of Core.cs, assert App.cs is found and Unrelated.cs is not

Test results

Stage Result
New targeted tests 10/10 passed
tests/test_multilang.py full 203 passed, 38 pre-existing failures (verified identical on unchanged main)
Full suite 1016 passed, 73 pre-existing failures (verified identical set on main via comm -23 diff — zero regressions)
ruff check on all 3 changed files clean
mypy on parser.py + query.py clean

Zero regressions. All fixes follow patterns already used in the codebase (inheritors_of bare-name fallback, File node extra tagging mirroring the Swift swift_kind pattern).

…h8205#295, tirth8205#310)

Two parser correctness fixes that restore metadata lost by v2.3.2.

Fix 1: Kotlin (and Java/C#/Python) annotation preservation (tirth8205#295)
-----------------------------------------------------------------
``_extract_functions`` already walked tree-sitter ``modifiers >
annotation`` children to collect decorators, but used the list ONLY for
test-kind detection and then discarded it.  Kotlin ``@Composable``,
``@HiltViewModel``, ``@Inject``; Java ``@Entity``, ``@Autowired``; C#
``[HttpGet]``; Python decorators — all were missing from the persisted
node.

``_extract_classes`` did not extract class-level annotations at all, so
``@HiltViewModel class MyViewModel``, ``@AndroidEntryPoint``, etc. lost
their annotation metadata.

Fix:
* New module-level ``_extract_annotation_list(node)`` helper that
  centralises the Java/Kotlin/C# ``modifiers`` walk and the Python
  ``decorated_definition`` walk.
* ``_extract_functions`` now persists annotations to
  ``node.modifiers`` (comma-separated string) AND
  ``node.extra["decorators"]`` (list) on every Function / Test node.
* ``_extract_classes`` calls the same helper to preserve class-level
  annotations.

Consumers can now write queries like "show me all @composable
functions" or "find @hiltviewmodel classes" — both currently impossible
on Kotlin codebases.

Fix 2: C# namespace-based importers_of resolution (tirth8205#310)
--------------------------------------------------------
C# ``using ACME.Core;`` directives produce an IMPORTS_FROM edge whose
target is the raw namespace string (``"ACME.Core"``), not a file path.
``importers_of`` looked edges up by file path and never matched a
namespace, so it returned ``[]`` for every .cs file.

Fix:
* New ``_extract_csharp_namespaces(root_node)`` helper handles both
  tree-sitter shapes — block (``namespace_declaration``) and C# 10+
  file-scoped (``file_scoped_namespace_declaration``).
* ``parse_bytes`` tags each C# File node with the namespaces it
  declares in ``extra["csharp_namespaces"]``.
* ``tools/query.py::query_graph`` adds a namespace fallback to
  ``importers_of``: when the target is a .cs file, look up its declared
  namespaces and also search edges_by_target for each namespace string.
  Deduped against the primary lookup so duplicates never appear.

This mirrors the existing bare-name fallback already used by
``callers_of`` and ``inheritors_of`` (see tirth8205#87).  No parser
restructuring needed — just an additional lookup path in the query
layer.

Tests added (tests/test_multilang.py)
-------------------------------------
TestKotlinAnnotations (5 tests):
* test_hilt_viewmodel_annotation_on_class
* test_composable_annotation_on_function
* test_multiple_annotations_on_function
* test_unannotated_function_has_none_modifiers (guard: no leak)
* test_test_annotation_still_triggers_test_kind (guard: pre-existing
  @test -> Test kind behavior preserved)

TestCSharpNamespaceResolution (5 tests):
* test_file_scoped_namespace_tagged_on_file_node (C# 10+ form)
* test_block_namespace_tagged_on_file_node (classic form)
* test_multiple_namespaces_in_one_file
* test_non_csharp_file_has_no_namespace_tag (guard: scope limit)
* test_importers_of_resolves_namespace_to_file (end-to-end)

Test results
------------
Stage 1 (new regression tests): 10/10 passed.
Stage 2 (tests/test_multilang.py full): 203 passed, 38 pre-existing
  failures (verified identical on unchanged main).
Stage 3 (tests/test_parser.py + tests/test_tools.py): adjacent tests
  match main baseline.
Stage 4 (full suite): 1016 passed, 73 pre-existing failures (verified
  identical set on unchanged main via comm -23 diff).
Stage 5 (ruff check on parser.py + query.py + test_multilang.py):
  clean.
Stage 6 (mypy on parser.py + query.py): clean.

Zero regressions.  All fixes follow patterns already used in the
codebase (inheritors_of namespace fallback, File node extra tagging
mirroring the Swift swift_kind pattern).
@azizur100389
Copy link
Copy Markdown
Contributor Author

Heads up on CI: the 3 failing checks (lint, type-check, test) are pre-existing baseline failures on upstream/main, not regressions from this PR. Verified by running each check on my branch and on a clean checkout of b0f8527 (the merge base) and getting identical failure sets.

Check Failure source Mine?
lint N999 Invalid module name for accidentally-committed macOS Finder duplicates (memory 2.py, analysis 2.py, enrich 2.py, exports 2.py/3.py, graph_diff 2.py, jedi_resolver 2.py, token_benchmark 2.py, memory 3.py) + 1× I001 import sort in main.py:9 + 1× F401 unused import in tools/review.py:9 No
type-check main.py:945: "FastMCP[Any]" has no attribute "_tool_manager" No
test (3.10/.11/.12/.13) 61 failures, including 6 errors in test_main.py::TestApplyToolFilter from the same _tool_manager issue, plus the same set that fails on unmodified main No

All three are addressed by @gzenz's existing #342 "chore: fix CI lint and type errors on main".

For the actual changes in this PR:

  • 10/10 new regression tests in TestKotlinAnnotations and TestCSharpNamespaceResolution pass locally on Python 3.10
  • Full tests/test_multilang.py shows 38 failures = same set that fails on unmodified main (zero regressions, verified via comm -23 diff)
  • ruff check clean on all 3 files I touched (parser.py, tools/query.py, tests/test_multilang.py)
  • mypy clean on parser.py and tools/query.py

Happy to rebase once #342 lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant