docs(csharp-scope): justify regex-based namespace-sibling detection

Record why `namespace-siblings.ts` uses regex over AST walks and
enumerate the known misses so the next reader has ground to stand on:

  * `global using static X.Y;` — no plain `using static` token.
  * Aliased `using static X = Y.Z;` — `=` breaks the pattern.
  * Attributed namespace declarations between `]` and `{`.
  * Multi-namespace files — first-wins attribution.
  * Preprocessor-gated namespace declarations — textual branch only.

Rationale: the pass is file-path-driven and the tree-sitter tree isn't
available at its call site (the orchestrator feeds raw fileContents);
re-parsing to count namespaces would cost more than the regex walk.
Refactor to AST-driven detection is deferred to a separate PR.

Mirrored the known-miss list into `csharp/index.ts`'s limitations
ledger so the operator-visible surface and the in-code justification
stay in sync.

No code change.
This commit is contained in:
Gergo Magyar 2026-04-22 09:13:35 +01:00
parent 2437ab10cf
commit 3a8a288ba7
2 changed files with 44 additions and 0 deletions

View file

@ -53,6 +53,19 @@
* 8. **Expression-bodied `=>` members** — handled by the method
* extractor, but receiver synthesis for `=> this.Field` shortcuts
* follows the same path as block-bodied methods.
* 9. **Regex-based namespace-sibling detection** —
* `namespace-siblings.ts` scans raw file content for `namespace X`
* and `using static X.Y` to compute same-namespace cross-file
* visibility. Known misses (each acknowledged in-file):
* a. `global using static X.Y;` is not detected.
* b. Aliased `using static X = Y.Z;` is not detected.
* c. Multi-namespace files: classes are attributed to the first
* declared namespace only.
* d. Preprocessor-gated `namespace` declarations are seen as
* whichever branch is textually present.
* See `namespace-siblings.ts`'s file-head comment for the full
* rationale; refactor to AST-driven detection is deferred to a
* separate PR.
*
* Shadow-harness corpus parity is the authoritative signal for which
* of these matter in practice. The CI parity gate blocks any PR that

View file

@ -19,6 +19,37 @@
* sibling classes into each Namespace scope's finalized bindings
* with `origin: 'namespace'` — a tier below `local` so a local
* declaration still shadows a cross-file sibling with the same name.
*
* ## Why regex and not the AST
*
* The pass is file-path-driven and only needs two pieces of info per
* file: which `namespace X` it declares, and which `using static X.Y`
* it pulls in. The tree-sitter tree isn't available at the pass's
* call site (the scope-resolution orchestrator feeds raw
* `fileContents` — re-parsing just to count namespaces would cost
* more than the regex walk). We accept the regex's known misses (see
* below) in exchange for a cheap, allocation-free pass.
*
* ## Known misses
*
* The regex-based detection silently skips:
* - `global using static X.Y;` (no plain `using static` token).
* - Aliased `using static X = Y.Z;` (the `=` breaks the pattern).
* - Attributed namespace declarations like `[assembly: X]
* namespace Y` — the regex still matches `namespace Y` but any
* trailing `[attr]` before `{` on the same line would fail.
* - Multi-namespace files: the `first-wins` attribution below
* groups all top-level classes under the first declared
* namespace; truly interleaved namespaces are rare but lose
* fidelity here.
* - `namespace` identifiers split by preprocessor `#if` /
* conditional compilation — the regex sees whichever branch is
* textually present.
*
* The limitations are mirrored in `csharp/index.ts`'s ledger so the
* operator-visible surface and the in-code justification stay in sync.
* If additional precision is required, refactor to consume the
* `@namespace.name` capture via the extractor (deferred — separate PR).
*/
import type { BindingRef, ParsedFile, Scope, ScopeId, SymbolDefinition } from 'gitnexus-shared';