Skip to content

fix(csharp): attribute declarations under file-scoped namespaces correctly#63

Open
alexander-heinrich wants to merge 1 commit into
trailofbits:mainfrom
alexander-heinrich:fix/file-scoped-namespace-attribution
Open

fix(csharp): attribute declarations under file-scoped namespaces correctly#63
alexander-heinrich wants to merge 1 commit into
trailofbits:mainfrom
alexander-heinrich:fix/file-scoped-namespace-attribution

Conversation

@alexander-heinrich

Copy link
Copy Markdown

Description

The C# parser only recognizes block-scoped namespaces (namespace X { ... }). File-scoped namespaces (namespace X;, C# 10+) are a different tree-sitter node type, so they were ignored: no NAMESPACE node was created, and every type in the file was attached directly to the module.

Fix

While walking the top level in _visit_module, watch for a file_scoped_namespace_declaration. When one appears, create the namespace unit and treat everything after it as a child of that namespace. (C# allows at most one file-scoped namespace per file, and nothing else can follow it at top level except its members, so this is safe.)

The namespace-unit creation shared by both syntaxes now lives in one helper, _create_namespace_unit.

Impact

Measured on MediatR v14.2.0 (~3K lines of C#; 42 of 43 source files use file-scoped namespaces) by running CSharpParser.parse_directory over src/ before and after the change. Before, trailmark recovers zero namespace nodes for the entire library, and all 52 types attach directly to their file modules. After, it recovers all 42 namespace declarations (9 distinct namespaces) and attributes all 52 types to their namespace.

Test plan

  • 6 new tests in test_csharp_parser.py (TestCSharpFileScopedNamespace, the existing SAMPLE_CODE fixture converted to file-scoped syntax): namespace node with exact id and location, module -> namespace edge, types contained by the namespace and not the module, using directives before the namespace still registered, methods inside the types still found
  • All existing tests pass (1,172 total)
  • ruff check / ruff format --check / ty check clean; mutation testing leaves no surviving mutants in touched code

…ectly

The C# grammar represents a file-scoped namespace (namespace X;) as
file_scoped_namespace_declaration, a distinct node type from
namespace_declaration with no body field: every top-level declaration
after it belongs to that namespace. _visit_top_level only matched
namespace_declaration, so types under a file-scoped namespace were
silently attributed to the module instead - no NAMESPACE CodeUnit, no
CONTAINS edge from the namespace.

Track the active file-scoped namespace while walking the top level in
_visit_module and route subsequent declarations through _visit_ns_child,
mirroring what _extract_namespace does with a braced body. Factor the
shared CodeUnit/CONTAINS-edge creation into _create_namespace_unit. If
the namespace node has no extractable name, later declarations keep
falling back to module attribution.
@CLAassistant

CLAassistant commented Jul 3, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants